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});

Next Steps