AWS Lambda入門 - サーバーレス関数の実装

中級 | 30分 read | 2025.01.10

AWS Lambdaとは

AWS Lambdaは、サーバー管理なしでコードを実行できるサーバーレスコンピューティングサービスです。リクエスト数と実行時間に基づく従量課金制です。

セットアップ

AWS SAM CLI

# SAM CLIインストール
brew install aws-sam-cli

# プロジェクト初期化
sam init --runtime nodejs20.x --name my-lambda

# ディレクトリ構造
my-lambda/
├── template.yaml
├── src/
│   └── handlers/
│       └── hello.ts
├── package.json
└── tsconfig.json

template.yaml

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

Globals:
  Function:
    Timeout: 30
    Runtime: nodejs20.x
    MemorySize: 256
    Environment:
      Variables:
        NODE_ENV: production

Resources:
  HelloFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: dist/handlers/hello.handler
      Events:
        Api:
          Type: Api
          Properties:
            Path: /hello
            Method: get
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2020

基本的なハンドラー

// src/handlers/hello.ts
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
  try {
    const name = event.queryStringParameters?.name || 'World';

    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        message: `Hello, ${name}!`,
        timestamp: new Date().toISOString(),
      }),
    };
  } catch (error) {
    console.error('Error:', error);

    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Internal Server Error' }),
    };
  }
};

DynamoDB連携

// src/handlers/users.ts
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  QueryCommand,
} from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.USERS_TABLE!;

// ユーザー取得
export const getUser = async (event: APIGatewayProxyEvent) => {
  const userId = event.pathParameters?.id;

  const { Item } = await docClient.send(new GetCommand({
    TableName: TABLE_NAME,
    Key: { userId },
  }));

  if (!Item) {
    return { statusCode: 404, body: JSON.stringify({ error: 'Not found' }) };
  }

  return { statusCode: 200, body: JSON.stringify(Item) };
};

// ユーザー作成
export const createUser = async (event: APIGatewayProxyEvent) => {
  const body = JSON.parse(event.body || '{}');
  const userId = crypto.randomUUID();

  await docClient.send(new PutCommand({
    TableName: TABLE_NAME,
    Item: {
      userId,
      ...body,
      createdAt: new Date().toISOString(),
    },
  }));

  return {
    statusCode: 201,
    body: JSON.stringify({ userId }),
  };
};

template.yaml (DynamoDB)

Resources:
  UsersTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: users
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: userId
          AttributeType: S
      KeySchema:
        - AttributeName: userId
          KeyType: HASH

  UsersFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: dist/handlers/users.getUser
      Environment:
        Variables:
          USERS_TABLE: !Ref UsersTable
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref UsersTable

ミドルウェアパターン(middy)

// src/handlers/protected.ts
import middy from '@middy/core';
import httpJsonBodyParser from '@middy/http-json-body-parser';
import httpErrorHandler from '@middy/http-error-handler';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

const baseHandler = async (
  event: APIGatewayProxyEvent & { body: any }
): Promise<APIGatewayProxyResult> => {
  const { name, email } = event.body;

  // ビジネスロジック
  const result = await processUser({ name, email });

  return {
    statusCode: 200,
    body: JSON.stringify(result),
  };
};

// ミドルウェアをチェーン
export const handler = middy(baseHandler)
  .use(httpJsonBodyParser())
  .use(httpErrorHandler());

認証(Cognito連携)

// src/middleware/auth.ts
import { CognitoJwtVerifier } from 'aws-jwt-verify';

const verifier = CognitoJwtVerifier.create({
  userPoolId: process.env.USER_POOL_ID!,
  tokenUse: 'access',
  clientId: process.env.CLIENT_ID!,
});

export const authMiddleware = () => ({
  before: async (request: any) => {
    const token = request.event.headers.authorization?.replace('Bearer ', '');

    if (!token) {
      throw { statusCode: 401, message: 'Unauthorized' };
    }

    try {
      const payload = await verifier.verify(token);
      request.event.user = payload;
    } catch (error) {
      throw { statusCode: 401, message: 'Invalid token' };
    }
  },
});

// 使用
export const handler = middy(baseHandler)
  .use(authMiddleware())
  .use(httpErrorHandler());

コールドスタート対策

// 接続の再利用
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';

// ハンドラーの外で初期化(再利用される)
const client = new DynamoDBClient({});

export const handler = async (event) => {
  // clientは再利用される
  // ...
};
# Provisioned Concurrency(コールドスタート回避)
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      AutoPublishAlias: live
      ProvisionedConcurrencyConfig:
        ProvisionedConcurrentExecutions: 5

デプロイ

# ビルド
sam build

# ローカルテスト
sam local invoke HelloFunction --event events/hello.json
sam local start-api

# デプロイ
sam deploy --guided

# CI/CDでのデプロイ
sam deploy \
  --stack-name my-app-prod \
  --s3-bucket my-deploy-bucket \
  --capabilities CAPABILITY_IAM \
  --no-confirm-changeset

関連記事

まとめ

AWS Lambdaは、スケーラブルなサーバーレスアプリケーションを構築できます。DynamoDB、API Gateway、Cognitoと組み合わせることで、フルスタックアプリを実現できます。

← Back to list