Adding Features¶
Adding a New API Endpoint¶
1. Define the route in CDK¶
Edit infrastructure/lib/task-time-stack.ts:
// Add resource under existing API structure
const newResource = api.root.addResource('api').addResource('my-feature');
// Add method with Cognito auth
newResource.addMethod('GET', new apigateway.LambdaIntegration(myLambda), {
authorizer: cognitoAuthorizer,
authorizationType: apigateway.AuthorizationType.COGNITO,
});
// Add OPTIONS for CORS
newResource.addMethod('OPTIONS', new apigateway.MockIntegration({
integrationResponses: [{
statusCode: '200',
responseParameters: {
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'",
'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,GET,POST,PUT,PATCH,DELETE'",
'method.response.header.Access-Control-Allow-Origin': "'*'",
},
}],
requestTemplates: { 'application/json': '{"statusCode": 200}' },
}), {
methodResponses: [{
statusCode: '200',
responseParameters: {
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Origin': true,
},
}],
});
2. Add handler logic¶
In the Lambda handler (e.g., infrastructure/lambda/task-sync/index.ts):
if (path === '/api/my-feature' && method === 'GET') {
// Handler logic here
return {
statusCode: 200,
headers: corsHeaders,
body: JSON.stringify({ data: result }),
};
}
3. Add types¶
Update packages/shared/src/types.ts with request/response types.
4. Deploy¶
pnpm build:lambdas && cd infrastructure && pnpm cdk deploy
Adding a New DynamoDB Table¶
1. Define table in CDK¶
Edit infrastructure/lib/task-time-stack.ts:
const myTable = new dynamodb.Table(this, 'MyTable', {
tableName: 'task-time-my-table',
partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
// Optional: Add GSI
myTable.addGlobalSecondaryIndex({
indexName: 'status-index',
partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'createdAt', type: dynamodb.AttributeType.STRING },
});
2. Grant Lambda access¶
myTable.grantReadWriteData(myLambda);
// Pass table name to Lambda
myLambda.addEnvironment('MY_TABLE_NAME', myTable.tableName);
3. Add types¶
Update packages/shared/src/types.ts:
export interface MyItem {
id: string;
status: string;
createdAt: string;
updatedAt: string;
}
4. Use in Lambda¶
const tableName = process.env.MY_TABLE_NAME!;
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const result = await docClient.send(new GetCommand({
TableName: tableName,
Key: { id: itemId },
}));
Adding a New Web Page¶
1. Create the page component¶
Create apps/web/app/(dashboard)/my-page/page.tsx:
'use client';
export default function MyPage() {
return (
<div>
<h1 className="text-2xl font-bold text-brand-foreground mb-6">My Page</h1>
{/* Page content */}
</div>
);
}
2. Add navigation item¶
Edit apps/web/app/(dashboard)/layout.tsx:
import { MyIcon } from '@heroicons/react/24/outline';
// Inside the navigation array (for internal users):
{ name: 'My Page', href: '/my-page', icon: MyIcon },
Adding Sub-Tabs¶
If your page needs tabs, create a layout with SubTabNav:
Create apps/web/app/(dashboard)/my-page/layout.tsx:
'use client';
import SubTabNav from '../../../components/SubTabNav';
export default function MyPageLayout({ children }: { children: React.ReactNode }) {
return (
<div>
<SubTabNav basePath="/my-page" tabs={[
{ name: 'Overview', href: 'overview' },
{ name: 'Details', href: 'details' },
]} />
{children}
</div>
);
}
Then create pages for each tab:
- apps/web/app/(dashboard)/my-page/overview/page.tsx
- apps/web/app/(dashboard)/my-page/details/page.tsx
Adding a New Lambda Function¶
1. Create Lambda directory¶
infrastructure/lambda/my-function/
└── index.ts
2. Write the handler¶
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
};
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
const { httpMethod: method, path } = event;
try {
// Route handling
if (path === '/api/my-endpoint' && method === 'GET') {
return {
statusCode: 200,
headers: corsHeaders,
body: JSON.stringify({ message: 'Hello' }),
};
}
return { statusCode: 404, headers: corsHeaders, body: '{"error":"Not found"}' };
} catch (error) {
console.error(error);
return { statusCode: 500, headers: corsHeaders, body: '{"error":"Internal server error"}' };
}
};
3. Define in CDK stack¶
Edit infrastructure/lib/task-time-stack.ts:
const myFunction = new lambda.Function(this, 'MyFunction', {
functionName: 'task-time-my-function',
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset(path.join(__dirname, '../lambda/my-function/dist')),
memorySize: 512,
timeout: cdk.Duration.seconds(30),
environment: {
// Environment variables
},
});
4. Build and deploy¶
The Lambda will be automatically bundled by infrastructure/scripts/build-lambdas.cjs when you run:
pnpm build:lambdas && cd infrastructure && pnpm cdk deploy