Skip to main content

Getting Started with AWS Lambda

This guide walks you through building a Zelt application on AWS Lambda from scratch.

Prerequisites​

  • Node.js v20 or higher (or Bun v1.0 or higher)
  • A package manager: pnpm (recommended), npm, or bun

Additional requirements for AWS Lambda:

Installation​

pnpm add @zeltjs/core @zeltjs/adapter-lambda
pnpm add -D @types/aws-lambda esbuild

Project Structure​

my-app/
├── src/
│   ├── entry/
│   │   ├── controllers/    # HTTP endpoints
│   │   └── commands/       # CLI commands
│   ├── services/           # Business logic
│   ├── configs/            # Configuration classes
│   ├── app.ts              # Application definition
│   ├── cli.ts              # CLI entry point
│   └── main.ts             # HTTP server entry point
├── package.json
└── tsconfig.json
DirectoryPurpose
entry/External entry points (HTTP, CLI)
services/Business logic, injected via DI
configs/Environment variables and settings

For AWS Lambda, also add template.yaml (SAM template) at the project root.

Hello World​

Step 1: Create the Controller​

Create src/entry/controllers/hello.controller.ts:

@Controller('/hello')
export class HelloController {
  @Get('/:name')
  greet(req = request()) {
    const name = req.pathParam('name');
    return { message: `Hello, ${name}!` };
  }
}

Step 2: Create the Application​

Create src/app.ts:

export const app = createApp([
  http({ controllers: [HelloController] }),
  lambdaHandler(),
]);

Step 3: Create the Lambda Handler​

Create src/handler.ts:

import { onLambda } from '@zeltjs/adapter-lambda';
import { app } from './app';

const lambdaApp = await onLambda(app);

export const handler = lambdaApp.lambdaHandler.create(lambdaApp.http);

onLambda() injects Lambda environment configs and initializes the runtime. It does not require an HTTP feature and preserves the namespaces of the features you declared.

lambdaHandler() publishes the lambdaHandler namespace by default. lambdaHandler.create(targetHttp) creates an API Gateway v2 function; pass { payloadFormat: '1.0' } to create a v1 function. Export the returned function for AWS to invoke. You can use any HTTP feature name and create separate handlers for multiple HTTP features.

Use lambdaHandler({ name: 'gateway' }) to expose lambdaApp.gateway.create(lambdaApp.http) instead. The usual runtime APIs, including shutdown() and get(Class), remain available.

Step 4: Configure SAM Template​

Create template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Timeout: 30
    Runtime: nodejs20.x

Resources:
  HelloFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: dist/
      Handler: handler.handler
      Events:
        Api:
          Type: HttpApi
          Properties:
            Path: /{proxy+}
            Method: ANY
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2022
        EntryPoints:
          - src/handler.ts

Outputs:
  ApiEndpoint:
    Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com"

Step 5: Deploy​

sam build
sam deploy --guided

API Gateway Versions​

The adapter supports both API Gateway versions:

export const handler = lambdaApp.lambdaHandler.create(lambdaApp.http);

REST API (v1)​

export const handler = lambdaApp.lambdaHandler.create(lambdaApp.http, { payloadFormat: '1.0' });

Warmup Option​

By default, onLambda() uses lazy initialization (warmup: false) to minimize cold start time. Controllers are resolved on the first request.

For eager initialization:

const lambdaApp = await onLambda(app, { warmup: true });
OptionBehaviorUse Case
warmup: false (default)Controllers resolved on first requestOptimized cold starts
warmup: trueAll controllers resolved at initializationProvisioned concurrency

Binary Responses​

The adapter automatically handles binary responses (images, audio, video, octet-stream) by encoding them as base64.

@Controller('/files')
export class FileController {
  @Get('/image')
  getImage() {
    const imageBuffer = new Uint8Array([/* ... */]);
    return response()
      .header('Content-Type', 'image/png')
      .body(imageBuffer);
  }
}

What's Next?​