A TypeScript CDK v2 library that provides constructs and helpers for building Lambda-powered serverless services on AWS. This library simplifies the creation of serverless architectures by automatically discovering handlers and creating the appropriate AWS resources.
npm install @faceteer/cdkRequirements: Node.js 20+
Create a Lambda service with automatic handler discovery:
import{LambdaService}from'@faceteer/cdk';import{Stack,StackProps}from'aws-cdk-lib';import{Construct}from'constructs';exportclassMyStackextendsStack{constructor(scope: Construct,id: string,props?: StackProps){super(scope,id,props);newLambdaService(this,'MyService',{handlersFolder: './src/handlers',});}}The main construct that orchestrates multiple Lambda functions. It automatically discovers handlers in a specified folder and creates the appropriate AWS resources (API Gateway, SQS queues, SNS topics, EventBridge rules, etc.).
The library supports 6 handler types, each creating different AWS integrations:
Creates Lambda functions integrated with API Gateway for REST endpoints.
// src/handlers/get-user.handler.tsimport{ApiHandler,SuccessResponse}from'@faceteer/cdk';exportconsthandler=ApiHandler({name: 'GetUser',method: 'GET',route: '/users/{userId}',pathParameters: ['userId'],memorySize: 512,},async(event)=>{const{ userId }=event.input.path;returnSuccessResponse({user: {id: userId}});});Creates Lambda functions triggered by SQS queue messages.
// src/handlers/process-user.handler.tsimport{QueueHandler}from'@faceteer/cdk';import{SQSClient}from'@aws-sdk/client-sqs';interfaceUser{userId: string;email: string;}exportconsthandler=QueueHandler({queueName: 'processUser',memorySize: 1024,timeout: 300,sqs: newSQSClient({region: 'us-east-1'}),validator: (body: any)=>bodyasUser,},async(event)=>{// Process messagesconsole.log(`Processing ${event.ValidMessages.length} messages`);return{retry: event.ValidMessages.filter(msg=>shouldRetry(msg)),};});Creates Lambda functions triggered by EventBridge events.
// src/handlers/user-created.handler.tsimport{EventHandler}from'@faceteer/cdk';exportconsthandler=EventHandler({name: 'UserCreatedEvent',eventPattern: {source: ['user.service'],'detail-type': ['User Created'],},eventBusName: 'default',},async(event)=>{console.log('User created:',event.detail);});Creates Lambda functions triggered by EventBridge scheduled rules.
// src/handlers/daily-cleanup.handler.tsimport{CronHandler}from'@faceteer/cdk';exportconsthandler=CronHandler({name: 'DailyCleanup',schedule: {expressionString: 'cron(0 2 * * ? *)',// Daily at 2 AM},},async(event)=>{console.log('Running daily cleanup...');});Creates Lambda functions triggered by SNS topic messages.
// src/handlers/email-notification.handler.tsimport{NotificationHandler}from'@faceteer/cdk';exportconsthandler=NotificationHandler({name: 'EmailNotification',topicName: 'email-notifications',memorySize: 256,},async(event)=>{console.log(`Processing ${event.ValidMessages.length} notifications`);});Creates Lambda functions triggered by a DynamoDB table's stream. The table is
referenced by name (see Tables); records are passed through raw (images
stay in DynamoDB AttributeValue form).
// src/handlers/user-changed.handler.tsimport{DynamoStreamHandler}from'@faceteer/cdk';exportconsthandler=DynamoStreamHandler({tableName: 'users',startingPosition: 'TRIM_HORIZON',},async(event)=>{constfailed=[];for(constrecordofevent.Records){try{console.log(record.eventName,record.dynamodb?.NewImage);}catch(error){failed.push(record);}}// Report records to retry. DynamoDB streams retry by checkpoint: Lambda// rewinds to the earliest failure and redelivers everything after it, so// your handler must be idempotent.return{retry: failed};});The library automatically discovers handlers using the extractHandlers function:
- Scans the specified handlers folder for files matching
*.handler.ts - Imports each handler file and extracts the exported
handlerobject - Creates handler definitions with metadata and file paths
- Handles naming conflicts by generating unique names
Standardized response helpers for consistent API responses:
import{SuccessResponse,FailedResponse}from'@faceteer/cdk';// Success responsereturnSuccessResponse({data: result});// Error responsereturnFailedResponse('User not found',404);Configure JWT or Lambda authorizers:
newLambdaService(this,'MyService',{handlersFolder: './src/handlers',authorizer: {// JWT AuthorizeridentitySource: ['$request.header.Authorization'],audience: ['api-client'],issuer: 'https://your-auth-provider.com',},// OR Lambda Authorizer// authorizer: {// fn: authorizerFunction,// identitySource: ['$request.header.Authorization'],// },});newLambdaService(this,'MyService',{handlersFolder: './src/handlers',domain: {certificate: certificate,domainName: 'api.example.com',route53Zone: hostedZone,},});newLambdaService(this,'MyService',{handlersFolder: './src/handlers',network: {vpc: vpc,vpcSubnets: {subnetType: SubnetType.PRIVATE_WITH_EGRESS},securityGroups: [securityGroup],},});Configure defaults that apply to all handlers:
newLambdaService(this,'MyService',{handlersFolder: './src/handlers',defaults: {memorySize: 512,timeout: 30,runtime: 'nodejs20.x',logRetentionDuration: LogRetentionDays.ONE_WEEK,},});Configure event buses for EventHandlers:
newLambdaService(this,'MyService',{handlersFolder: './src/handlers',eventBuses: {'user-events': EventBus.fromEventBusName(this,'UserBus','user-events'),'order-events': newEventBus(this,'OrderBus'),},});Configure the source tables for DynamoStreamHandlers. Each DynamoStreamHandler
references a table by the key used here. The table must have a stream enabled;
the framework does not create or own the table.
newLambdaService(this,'MyService',{handlersFolder: './src/handlers',tables: {// key matches `tableName` in the handler definitionusers: usersTable,// a dynamodb.Table created elsewhere, or// Table.fromTableAttributes(this, 'Users', { tableName, tableStreamArn })// to reference an existing table},});Handlers support custom validation functions for type-safe input processing:
interfaceCreateUserRequest{email: string;name: string;}// Custom validation functionfunctionvalidateCreateUser(body: unknown): CreateUserRequest{if(!body||typeofbody!=='object'){thrownewError('Body must be an object');}const{ email, name }=bodyasany;if(!email||typeofemail!=='string'){thrownewError('Email is required and must be a string');}if(!name||typeofname!=='string'){thrownewError('Name is required and must be a string');}return{ email, name };}exportconsthandler=ApiHandler({name: 'CreateUser',method: 'POST',route: '/users',validators: {body: validateCreateUser,},},async(event)=>{// event.input.body is now typed and validatedconstuser=awaitcreateUser(event.input.body);returnSuccessResponse({ user });});Add environment variables to all functions in a service:
constservice=newLambdaService(this,'MyService',{/* ... */});service.addEnvironment('DATABASE_URL',databaseUrl);service.addEnvironment('API_KEY',apiKey);The queue handler supports local SQS development using tools like ElasticMQ. Set the SQS_ENDPOINT environment variable to point to your local SQS instance:
# For ElasticMQ running locallyexport SQS_ENDPOINT=http://localhost:9324
# Or in your .env file
SQS_ENDPOINT=http://localhost:9324When SQS_ENDPOINT is set, queue operations will use the custom endpoint instead of AWS SQS. This allows you to develop and test queue functionality locally without connecting to AWS.
src/
├── handlers/
│ ├── api/
│ │ ├── get-users.handler.ts
│ │ └── create-user.handler.ts
│ ├── queues/
│ │ └── process-user.handler.ts
│ ├── events/
│ │ └── user-created.handler.ts
│ └── crons/
│ └── daily-cleanup.handler.ts
└── lib/
└── my-stack.ts
The library includes comprehensive test utilities. Run tests with:
npm testnpm run build- Build TypeScript filesnpm run test- Run tests with coveragenpm run test:ci- Run tests in CI mode
MIT