メインコンテンツまでスキップ

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(name = pathParam('name')) {
    return { message: `Hello, ${name}!` };
  }
}

Step 2: Create the Application

Create src/app.ts:

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

Step 3: Create the Lambda Handler

Create src/handler.ts:

const lambdaApp = await onLambda(app);

export const handler = lambdaApp.handler;

The onLambda() function prepares your app for the Lambda runtime. It returns:

  • handler — API Gateway v2 (HTTP API) handler
  • handlerV1 — API Gateway v1 (REST API) handler
  • shutdown() — Gracefully shuts down the application
  • get<T>(Class) — Resolves a service from the DI container

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.handler;

REST API (v1)

export const handler = lambdaApp.handlerV1;

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?