AWS Lambda Deployment
Deploy your MoroJS applications to AWS Lambda for serverless scalability. Automatic scaling, pay-per-use pricing, and seamless AWS integration.
Quick Start
MoroJS provides native AWS Lambda support with automatic request/response transformation and optimized cold start performance.
Basic Lambda Function
typescript
1// index.ts
2import { createAppLambda } from '@morojs/moro';
3
4const app = createAppLambda();
5
6app.get('/', () => {
7 return { message: 'Hello from AWS Lambda!' };
8});
9
10app.post('/users', {
11 body: z.object({
12 name: z.string(),
13 email: z.string().email()
14 }),
15 handler: ({ body }) => {
16 return {
17 success: true,
18 user: body
19 };
20 }
21});
22
23// Export handler for Lambda
24export const handler = app.getHandler();
Lambda Benefits
- • Automatic scaling from zero to thousands
- • Pay only for actual usage
- • Integrated with AWS services
- • Built-in monitoring and logging
- • No server management required
Project Setup
Project Structure
typescript
1my-moro-lambda/
2├── src/
3│ ├── handlers/
4│ │ ├── users.ts
5│ │ ├── auth.ts
6│ │ └── api.ts
7│ ├── lib/
8│ │ ├── db.ts
9│ │ └── utils.ts
10│ └── index.ts
11├── package.json
12├── serverless.yml
13├── webpack.config.js
14└── tsconfig.json
15
16// package.json
17{
18 "name": "my-moro-lambda",
19 "version": "1.0.0",
20 "scripts": {
21 "build": "webpack",
22 "deploy": "serverless deploy",
23 "dev": "serverless offline",
24 "logs": "serverless logs -f api"
25 },
26 "dependencies": {
27 "@morojs/moro": "^1.0.0",
28 "zod": "^3.22.0"
29 },
30 "devDependencies": {
31 "serverless": "^3.0.0",
32 "serverless-webpack": "^5.0.0",
33 "serverless-offline": "^12.0.0",
34 "webpack": "^5.0.0",
35 "typescript": "^5.0.0"
36 }
37}
Serverless Framework Configuration
typescript
1# serverless.yml
2service: my-moro-api
3
4provider:
5 name: aws
6 runtime: nodejs18.x
7 region: us-east-1
8 memorySize: 512
9 timeout: 30
10 environment:
11 NODE_ENV: production
12 DATABASE_URL: ${env:DATABASE_URL}
13 JWT_SECRET: ${env:JWT_SECRET}
14
15 # IAM permissions
16 iamRoleStatements:
17 - Effect: Allow
18 Action:
19 - dynamodb:Query
20 - dynamodb:Scan
21 - dynamodb:GetItem
22 - dynamodb:PutItem
23 - dynamodb:UpdateItem
24 - dynamodb:DeleteItem
25 Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:service}-*"
26
27plugins:
28 - serverless-webpack
29 - serverless-offline
30
31custom:
32 webpack:
33 webpackConfig: ./webpack.config.js
34 includeModules: true
35 packager: npm
36
37functions:
38 api:
39 handler: src/index.handler
40 events:
41 - http:
42 path: /{proxy+}
43 method: ANY
44 cors: true
45 - http:
46 path: /
47 method: ANY
48 cors: true
49
50resources:
51 Resources:
52 # DynamoDB tables
53 UsersTable:
54 Type: AWS::DynamoDB::Table
55 Properties:
56 TableName: ${self:service}-users
57 AttributeDefinitions:
58 - AttributeName: id
59 AttributeType: S
60 KeySchema:
61 - AttributeName: id
62 KeyType: HASH
63 BillingMode: PAY_PER_REQUEST
Advanced Lambda Configuration
Optimized Lambda Setup
typescript
1// src/index.ts
2import { createAppLambda } from '@morojs/moro';
3import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
4import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
5
6// Initialize AWS clients outside handler for connection reuse
7const dynamoClient = new DynamoDBClient({ region: process.env.AWS_REGION });
8const docClient = DynamoDBDocumentClient.from(dynamoClient);
9
10const app = createAppLambda({
11 // Lambda-specific configuration
12 lambda: {
13 // Optimize for cold starts
14 optimizeForColdStart: true,
15
16 // Connection reuse
17 reuseConnections: true,
18
19 // Memory optimization
20 memorySize: 512,
21 timeout: 30,
22
23 // Environment-specific settings
24 environment: process.env.NODE_ENV,
25
26 // AWS service integrations
27 aws: {
28 dynamodb: docClient,
29 region: process.env.AWS_REGION
30 }
31 },
32
33 // CORS for API Gateway
34 cors: {
35 origin: process.env.ALLOWED_ORIGINS?.split(',') || ['*'],
36 credentials: true,
37 methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
38 },
39
40 // Error handling
41 errorHandler: (error, context) => {
42 console.error('Lambda error:', error);
43
44 return {
45 statusCode: 500,
46 body: JSON.stringify({
47 error: 'Internal server error',
48 requestId: context.awsRequestId
49 })
50 };
51 }
52});
53
54// Global middleware for Lambda context
55app.use(async (context, next) => {
56 // Add AWS context to request
57 context.aws = {
58 requestId: context.awsRequestId,
59 functionName: context.functionName,
60 functionVersion: context.functionVersion,
61 memoryLimitInMB: context.memoryLimitInMB,
62 remainingTimeInMillis: context.getRemainingTimeInMillis()
63 };
64
65 await next();
66});
67
68// API routes
69app.get('/health', {
70 handler: ({ aws }) => ({
71 status: 'healthy',
72 requestId: aws.requestId,
73 timestamp: new Date().toISOString()
74 })
75});
76
77export const handler = app.getHandler();
DynamoDB Integration
typescript
1// lib/dynamodb.ts
2import { DynamoDBDocumentClient, GetCommand, PutCommand, QueryCommand, ScanCommand } from '@aws-sdk/lib-dynamodb';
3
4export class DynamoDBService {
5 constructor(private client: DynamoDBDocumentClient) {}
6
7 async getUser(id: string) {
8 const command = new GetCommand({
9 TableName: process.env.USERS_TABLE,
10 Key: { id }
11 });
12
13 const result = await this.client.send(command);
14 return result.Item;
15 }
16
17 async createUser(user: any) {
18 const command = new PutCommand({
19 TableName: process.env.USERS_TABLE,
20 Item: {
21 ...user,
22 id: crypto.randomUUID(),
23 createdAt: new Date().toISOString(),
24 updatedAt: new Date().toISOString()
25 }
26 });
27
28 await this.client.send(command);
29 return command.input.Item;
30 }
31
32 async listUsers(limit = 20) {
33 const command = new ScanCommand({
34 TableName: process.env.USERS_TABLE,
35 Limit: limit
36 });
37
38 const result = await this.client.send(command);
39 return result.Items || [];
40 }
41}
42
43// Usage in routes
44const db = new DynamoDBService(docClient);
45
46app.get('/users', {
47 handler: async () => {
48 const users = await db.listUsers();
49 return users;
50 }
51});
52
53app.get('/users/:id', {
54 params: z.object({
55 id: z.string().uuid()
56 }),
57 handler: async ({ params }) => {
58 const user = await db.getUser(params.id);
59 if (!user) {
60 throw new Error('User not found');
61 }
62 return user;
63 }
64});
65
66app.post('/users', {
67 body: z.object({
68 name: z.string(),
69 email: z.string().email()
70 }),
71 handler: async ({ body }) => {
72 const user = await db.createUser(body);
73 return user;
74 }
75});
Performance Optimization
Cold Start Optimization
typescript
1// Webpack configuration for smaller bundles
2// webpack.config.js
3const path = require('path');
4
5module.exports = {
6 target: 'node',
7 mode: 'production',
8 entry: './src/index.ts',
9 output: {
10 path: path.resolve(__dirname, 'dist'),
11 filename: 'index.js',
12 libraryTarget: 'commonjs2'
13 },
14 resolve: {
15 extensions: ['.ts', '.js']
16 },
17 module: {
18 rules: [
19 {
20 test: /\.ts$/,
21 use: 'ts-loader',
22 exclude: /node_modules/
23 }
24 ]
25 },
26 externals: {
27 // Exclude AWS SDK (available in Lambda runtime)
28 'aws-sdk': 'aws-sdk',
29 '@aws-sdk/client-dynamodb': '@aws-sdk/client-dynamodb'
30 },
31 optimization: {
32 minimize: true
33 }
34};
35
36// Connection pooling for Lambda
37import { Connection } from 'mysql2/promise';
38
39let connection: Connection | null = null;
40
41const getConnection = async (): Promise<Connection> => {
42 if (!connection) {
43 connection = await mysql.createConnection({
44 host: process.env.DB_HOST,
45 user: process.env.DB_USER,
46 password: process.env.DB_PASSWORD,
47 database: process.env.DB_NAME,
48 // Optimize for Lambda
49 acquireTimeout: 60000,
50 timeout: 60000,
51 reconnect: true
52 });
53 }
54 return connection;
55};
56
57// Use connection in handlers
58app.get('/users', {
59 handler: async () => {
60 const conn = await getConnection();
61 const [rows] = await conn.execute('SELECT * FROM users');
62 return rows;
63 }
64});
Memory and Timeout Configuration
typescript
1# serverless.yml - Environment-specific settings
2provider:
3 name: aws
4 runtime: nodejs18.x
5
6 # Development settings
7 memorySize: ${opt:stage, 'dev'}
8 timeout: ${opt:stage, 'dev'}
9
10custom:
11 memorySize:
12 dev: 256 # Smaller memory for development
13 staging: 512 # Medium memory for staging
14 prod: 1024 # Higher memory for production
15
16 timeout:
17 dev: 15 # Shorter timeout for development
18 staging: 30 # Medium timeout for staging
19 prod: 60 # Longer timeout for production
20
21functions:
22 api:
23 handler: src/index.handler
24 memorySize: ${self:custom.memorySize.${opt:stage, 'dev'}}
25 timeout: ${self:custom.timeout.${opt:stage, 'dev'}}
26
27 # Reserved concurrency to prevent cost spikes
28 reservedConcurrency: 10
29
30 # Provisioned concurrency for consistent performance
31 provisionedConcurrency: 2
32
33 environment:
34 # Lambda-specific optimizations
35 AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1
36
37 events:
38 - http:
39 path: /{proxy+}
40 method: ANY
41 cors: true
42 # Request validation at API Gateway level
43 request:
44 parameters:
45 headers:
46 Authorization: false
47 # Response caching
48 caching:
49 enabled: true
50 ttlInSeconds: 300
Deployment Process
Deploy with Serverless Framework
typescript
1# Install Serverless CLI
2npm install -g serverless
3
4# Configure AWS credentials
5serverless config credentials --provider aws --key YOUR_KEY --secret YOUR_SECRET
6
7# Deploy to development
8serverless deploy --stage dev
9
10# Deploy to production
11serverless deploy --stage prod
12
13# Deploy single function
14serverless deploy function --function api
15
16# Remove deployment
17serverless remove --stage dev
18
19# View logs
20serverless logs --function api --tail
21
22# Invoke function locally
23serverless invoke local --function api --data '{"httpMethod": "GET", "path": "/health"}'
24
25# Package.json scripts
26{
27 "scripts": {
28 "deploy:dev": "serverless deploy --stage dev",
29 "deploy:prod": "serverless deploy --stage prod",
30 "logs:dev": "serverless logs --function api --stage dev --tail",
31 "logs:prod": "serverless logs --function api --stage prod --tail",
32 "remove:dev": "serverless remove --stage dev",
33 "invoke:local": "serverless invoke local --function api",
34 "offline": "serverless offline"
35 }
36}
CI/CD Pipeline
typescript
1# .github/workflows/deploy.yml
2name: Deploy to AWS Lambda
3
4on:
5 push:
6 branches: [main, develop]
7
8jobs:
9 deploy:
10 runs-on: ubuntu-latest
11
12 steps:
13 - uses: actions/checkout@v3
14
15 - name: Setup Node.js
16 uses: actions/setup-node@v3
17 with:
18 node-version: '18'
19 cache: 'npm'
20
21 - name: Install dependencies
22 run: npm ci
23
24 - name: Build
25 run: npm run build
26
27 - name: Deploy to staging
28 if: github.ref == 'refs/heads/develop'
29 env:
30 AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
31 AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
32 run: serverless deploy --stage staging
33
34 - name: Deploy to production
35 if: github.ref == 'refs/heads/main'
36 env:
37 AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
38 AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
39 run: serverless deploy --stage prod
40
41 - name: Run integration tests
42 env:
43 API_ENDPOINT: ${{ steps.deploy.outputs.endpoint }}
44 run: npm run test:integration
Monitoring & Observability
CloudWatch Integration
typescript
1// Custom metrics and logging
2import { CloudWatchClient, PutMetricDataCommand } from '@aws-sdk/client-cloudwatch';
3
4const cloudwatch = new CloudWatchClient({ region: process.env.AWS_REGION });
5
6// Custom middleware for metrics
7app.use(async (context, next) => {
8 const startTime = Date.now();
9
10 try {
11 await next();
12
13 // Success metrics
14 await cloudwatch.send(new PutMetricDataCommand({
15 Namespace: 'MoroJS/Lambda',
16 MetricData: [
17 {
18 MetricName: 'RequestDuration',
19 Value: Date.now() - startTime,
20 Unit: 'Milliseconds',
21 Dimensions: [
22 { Name: 'FunctionName', Value: context.functionName },
23 { Name: 'Path', Value: context.path }
24 ]
25 },
26 {
27 MetricName: 'RequestCount',
28 Value: 1,
29 Unit: 'Count',
30 Dimensions: [
31 { Name: 'Status', Value: 'Success' }
32 ]
33 }
34 ]
35 }));
36
37 } catch (error) {
38 // Error metrics
39 await cloudwatch.send(new PutMetricDataCommand({
40 Namespace: 'MoroJS/Lambda',
41 MetricData: [
42 {
43 MetricName: 'ErrorCount',
44 Value: 1,
45 Unit: 'Count',
46 Dimensions: [
47 { Name: 'ErrorType', Value: error.constructor.name }
48 ]
49 }
50 ]
51 }));
52
53 throw error;
54 }
55});
56
57// Health check with detailed metrics
58app.get('/health', {
59 handler: async ({ aws }) => {
60 return {
61 status: 'healthy',
62 timestamp: new Date().toISOString(),
63 lambda: {
64 requestId: aws.requestId,
65 functionName: aws.functionName,
66 functionVersion: aws.functionVersion,
67 memoryLimit: aws.memoryLimitInMB,
68 remainingTime: aws.remainingTimeInMillis
69 },
70 environment: process.env.NODE_ENV
71 };
72 }
73});