NestJSでサーバーレスアプリケーションを実装してみた

記事タイトルとURLをコピーする

はじめに

アプリケーションサービス部の遠藤です!

この記事では、TypeScriptベースのNode.jsフレームワーク「NestJS」を使用して、AWS Lambda + API Gateway上で動作するサーバーレスAPIを構築する方法を詳しく解説します。

実際に以下の機能を持つECサイト風のAPIを構築しながら、NestJSの基本概念からAWSデプロイまでを一通り学べる内容となっています。

構築するAPI機能

  • ヘルスチェック API
  • ユーザー管理 API(CRUD操作)
  • 商品管理 API(在庫管理含む)
  • 注文管理 API(在庫連動)
  • Swagger UI による API ドキュメント

技術スタック

  • フレームワーク: NestJS
  • 言語: TypeScript
  • ランタイム: Node.js 18
  • クラウド: AWS Lambda + API Gateway
  • デプロイ: AWS SAM
  • ドキュメント: Swagger/OpenAPI

NestJSとは?

NestJSは、効率的でスケーラブルなNode.jsサーバーサイドアプリケーションを構築するためのフレームワークです。宣言型プログラミングのパラダイムを採用し、開発者が「何をしたいか」に集中できる環境を提供します。

NestJSの特徴

  1. TypeScript ファースト

    • TypeScriptで書かれており、型安全性を提供
    • JavaScriptでも使用可能
  2. 宣言型プログラミング

    • デコレータベースの宣言的な開発
    • 「どのようにするか」ではなく「何をしたいか」に集中
  3. デコレータベース

    • Angular風のデコレータを使用
    • メタデータ駆動の開発
  4. モジュラーアーキテクチャ

    • 機能ごとにモジュール化
    • 依存性注入(DI)をサポート
  5. 豊富なエコシステム

    • Express/Fastify対応
    • GraphQL、WebSocket、マイクロサービス対応

宣言型プログラミングの特徴

NestJSでは、複雑な処理も宣言的に記述できます。

// 従来の命令型アプローチ(Express.js)
app.get('/users', (req, res) => {
  // バリデーション処理を手動で実装
  if (!req.query.page || isNaN(req.query.page)) {
    return res.status(400).json({ error: 'Invalid page parameter' });
  }
  
  // 認証チェックを手動で実装
  const token = req.headers.authorization;
  if (!token || !verifyToken(token)) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  // ビジネスロジック
  const users = getUsersFromDatabase(req.query.page);
  res.json(users);
});

// NestJSの宣言型アプローチ
@Controller('users')
export class UsersController {
  @Get()
  @UseGuards(JwtAuthGuard)  // 認証が必要であることを宣言
  @ApiOperation({ summary: 'ユーザー一覧を取得' })
  async findAll(
    @Query('page', ParseIntPipe) page: number = 1  // バリデーションを宣言
  ): Promise<User[]> {
    // 「ユーザー一覧を取得したい」ことを宣言
    return this.usersService.findAll(page);
  }
}

この宣言型アプローチにより、コードの可読性、保守性、テスタビリティが大幅に向上します。

基本概念

1. モジュール(Module)

アプリケーションの機能をグループ化する単位です。

@Module({
  imports: [UsersModule, ProductsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

2. コントローラー(Controller)

HTTPリクエストを処理し、レスポンスを返す役割を担います。

@Controller('users')
export class UsersController {
  @Get()
  findAll(): string {
    return 'This action returns all users';
  }
}

3. サービス(Service)

ビジネスロジックを実装するクラスです。

@Injectable()
export class UsersService {
  findAll(): User[] {
    return this.users;
  }
}

4. DTO(Data Transfer Object)

データの形式を定義し、バリデーションを行います。

export class CreateUserDto {
  @IsNotEmpty()
  @IsString()
  username: string;

  @IsEmail()
  email: string;
}

NestJSの主要ユースケース

NestJSは様々な場面で活用できる汎用性の高いフレームワークです。以下に主要なユースケースを紹介します。

1. エンタープライズWebアプリケーション

大規模なチーム開発や複雑なビジネスロジックを持つアプリケーションに最適です。

// 複雑な権限管理を宣言的に実装
@Controller('admin/reports')
@UseGuards(AuthGuard, RoleGuard)
@Roles('admin', 'manager')
export class AdminReportsController {
  @Get('sales')
  @Permissions('reports:sales:read')
  @ApiOperation({ summary: '売上レポートを取得' })
  async getSalesReport(
    @Query() query: SalesReportQueryDto,
    @CurrentUser() user: User,
  ): Promise<SalesReportDto> {
    return this.reportsService.generateSalesReport(query, user);
  }
}

適用場面: ERP、CRM、人事管理システム、在庫管理システム

2. マイクロサービスアーキテクチャ

サービス間通信や分散システムの構築に優れています。

@Controller()
export class OrdersController {
  constructor(
    @Inject('USER_SERVICE') private userService: ClientProxy,
    @Inject('PAYMENT_SERVICE') private paymentService: ClientProxy,
  ) {}

  @Post('orders')
  async createOrder(@Body() createOrderDto: CreateOrderDto) {
    // 他のサービスとの連携を宣言的に記述
    const user = await this.userService
      .send('get_user', { id: createOrderDto.userId })
      .toPromise();

    const payment = await this.paymentService
      .send('process_payment', { 
        amount: createOrderDto.totalAmount,
        userId: createOrderDto.userId 
      })
      .toPromise();

    return this.ordersService.create(createOrderDto);
  }
}

適用場面: ECサイト、金融システム、IoTプラットフォーム、SaaSアプリケーション

3. RESTful API / GraphQL API

モバイルアプリやSPAのバックエンドAPIとして活用できます。

// RESTful API
@Controller('api/v1/products')
@ApiTags('products')
export class ProductsController {
  @Get()
  @ApiOperation({ summary: '商品一覧を取得' })
  @ApiQuery({ name: 'category', required: false })
  async findAll(
    @Query('category') category?: string,
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1,
  ): Promise<PaginatedProductsDto> {
    return this.productsService.findAll({ category, page });
  }
}

// GraphQL API
@Resolver(() => Product)
export class ProductsResolver {
  @Query(() => [Product])
  @UseGuards(GqlAuthGuard)
  async products(
    @Args('filter', { nullable: true }) filter?: ProductFilterInput,
  ): Promise<Product[]> {
    return this.productsService.findAll(filter);
  }
}

適用場面: モバイルアプリバックエンド、外部システム連携API、パートナー向けAPI

4. リアルタイムアプリケーション

WebSocketを使用したリアルタイム通信アプリケーションの構築が可能です。

@WebSocketGateway()
export class ChatGateway {
  @SubscribeMessage('send_message')
  @UseGuards(WsAuthGuard)
  @UsePipes(new ValidationPipe())
  async handleMessage(
    @MessageBody() data: SendMessageDto,
    @ConnectedSocket() client: Socket,
  ): Promise<void> {
    const message = await this.chatService.createMessage({
      ...data,
      userId: client.data.userId,
    });

    this.server.to(data.roomId).emit('new_message', message);
  }
}

適用場面: チャットアプリ、ライブ配信、リアルタイム協業ツール、ゲームサーバー

5. サーバーレスアプリケーション(今回の実装)

AWS LambdaやAzure Functionsでの実行に対応しています。

// Lambda関数として動作するNestJSアプリケーション
export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> => {
  const server = await bootstrap();
  return server(event, context);
};

適用場面: イベント駆動アーキテクチャ、コスト最適化重視のAPI、スケーラブルなバッチ処理

ユースケース選択の指針

✅ NestJSが適している場面

  • 大規模・複雑なアプリケーション
  • チーム開発(複数の開発者が関わる)
  • 長期保守が必要なプロジェクト
  • TypeScriptを活用したい場合
  • エンタープライズ要件がある場合
  • テスタビリティを重視する場合

❌ NestJSが適さない場面

  • シンプルな静的サイト
  • プロトタイプ・MVPの迅速な開発
  • 学習コストを避けたい場合
  • 軽量さを最優先する場合

プロジェクトセットアップ

1. 初期設定

# プロジェクト初期化
npm init -y

# NestJS関連の依存関係をインストール
npm install @nestjs/core @nestjs/common @nestjs/platform-express @nestjs/swagger reflect-metadata rxjs class-validator class-transformer

# 開発用依存関係をインストール
npm install -D @nestjs/cli @types/node typescript ts-node nodemon

# AWS Lambda用の依存関係をインストール
npm install aws-lambda @vendia/serverless-express express
npm install -D @types/aws-lambda @types/express

2. TypeScript設定

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2020",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true
  }
}

3. package.json スクリプト

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/main.js",
    "start:dev": "nodemon --exec ts-node src/main.ts",
    "start:prod": "node dist/main.js"
  }
}

アプリケーション構造の実装

アプリケーションのエントリーファイルは、ローカル開発用とAWS Lambda用で2種類実装します。

1. メインアプリケーションファイル

src/main.ts(ローカル開発用)

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // CORS設定
  app.enableCors();
  
  // バリデーションパイプの設定
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }));

  // Swagger設定
  const config = new DocumentBuilder()
    .setTitle('NestJS API')
    .setDescription('AWS上で動作するNestJS API')
    .setVersion('1.0')
    .addTag('users')
    .addTag('products')
    .addTag('orders')
    .build();
  
  const document = SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api', app, document);

  const port = process.env.PORT || 3000;
  await app.listen(port);
  console.log(`Application is running on: http://localhost:${port}`);
}

bootstrap();

src/lambda.ts(AWS Lambda用)

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ExpressAdapter } from '@nestjs/platform-express';
import { Context, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { configure } from '@vendia/serverless-express';
import * as express from 'express';
import { AppModule } from './app.module';

let cachedServer: any;

async function bootstrap() {
  if (!cachedServer) {
    console.log('Initializing NestJS application...');
    
    const expressApp = express();
    const adapter = new ExpressAdapter(expressApp);
    
    const app = await NestFactory.create(AppModule, adapter, {
      logger: ['error', 'warn', 'log'],
    });
    
    // CORS設定
    app.enableCors({
      origin: '*',
      methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
      allowedHeaders: 'Content-Type,Authorization,X-Requested-With',
    });
    
    // バリデーションパイプの設定
    app.useGlobalPipes(new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      disableErrorMessages: false,
    }));

    // Swagger設定
    const config = new DocumentBuilder()
      .setTitle('NestJS API on AWS')
      .setDescription('AWS Lambda + API Gateway で動作するNestJS API')
      .setVersion('1.0')
      .addTag('health', 'ヘルスチェック関連のAPI')
      .addTag('users', 'ユーザー管理API')
      .addTag('products', '商品管理API')
      .addTag('orders', '注文管理API')
      .build();
    
    const document = SwaggerModule.createDocument(app, config);
    SwaggerModule.setup('api', app, document, {
      customSiteTitle: 'NestJS API Documentation',
      customCss: '.swagger-ui .topbar { display: none }',
    });

    await app.init();
    
    cachedServer = configure({ 
      app: expressApp,
      logSettings: {
        level: 'info'
      }
    });
    
    console.log('NestJS application initialized successfully');
  }

  return cachedServer;
}

export const handler = async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> => {
  console.log('Lambda handler invoked', {
    httpMethod: event.httpMethod,
    path: event.path,
    pathParameters: event.pathParameters,
    queryStringParameters: event.queryStringParameters,
  });

  try {
    const server = await bootstrap();
    const result = await server(event, context);
    
    console.log('Request processed successfully', {
      statusCode: result.statusCode,
      path: event.path,
    });
    
    return result;
  } catch (error) {
    console.error('Error processing request:', error);
    
    return {
      statusCode: 500,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type,Authorization,X-Requested-With',
      },
      body: JSON.stringify({
        error: 'Internal Server Error',
        message: 'An error occurred while processing the request',
        timestamp: new Date().toISOString(),
      }),
    };
  }
};

2. アプリケーションモジュール

src/app.module.ts

import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { ProductsModule } from './products/products.module';
import { OrdersModule } from './orders/orders.module';
import { HealthModule } from './health/health.module';

@Module({
  imports: [
    UsersModule,
    ProductsModule,
    OrdersModule,
    HealthModule,
  ],
})
export class AppModule {}

API実装の詳細

1. ヘルスチェック API

システムの状態を確認するためのシンプルなAPIです。

src/health/health.controller.ts

import { Controller, Get } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';

@ApiTags('health')
@Controller('health')
export class HealthController {
  @Get()
  @ApiOperation({ summary: 'ヘルスチェック' })
  @ApiResponse({ status: 200, description: 'アプリケーションが正常に動作中' })
  getHealth() {
    return {
      status: 'ok',
      timestamp: new Date().toISOString(),
      uptime: process.uptime(),
      environment: process.env.NODE_ENV || 'development',
    };
  }

  @Get('ready')
  @ApiOperation({ summary: 'レディネスチェック' })
  @ApiResponse({ status: 200, description: 'アプリケーションがリクエストを受け付け可能' })
  getReady() {
    return {
      status: 'ready',
      timestamp: new Date().toISOString(),
    };
  }
}

2. ユーザー管理 API

CRUD操作を含む完全なユーザー管理機能を実装します。

DTOの定義

// src/users/dto/user.dto.ts
import { IsEmail, IsNotEmpty, IsString, IsOptional, MinLength } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
  @ApiProperty({ description: 'ユーザー名', example: 'john_doe' })
  @IsNotEmpty()
  @IsString()
  username: string;

  @ApiProperty({ description: 'メールアドレス', example: 'john@example.com' })
  @IsEmail()
  email: string;

  @ApiProperty({ description: 'パスワード', example: 'password123' })
  @IsNotEmpty()
  @MinLength(6)
  password: string;

  @ApiProperty({ description: '氏名', example: 'John Doe', required: false })
  @IsOptional()
  @IsString()
  fullName?: string;
}

export class UpdateUserDto {
  @ApiProperty({ description: 'ユーザー名', example: 'john_doe', required: false })
  @IsOptional()
  @IsString()
  username?: string;

  @ApiProperty({ description: 'メールアドレス', example: 'john@example.com', required: false })
  @IsOptional()
  @IsEmail()
  email?: string;

  @ApiProperty({ description: '氏名', example: 'John Doe', required: false })
  @IsOptional()
  @IsString()
  fullName?: string;
}

サービスの実装

// src/users/users.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { CreateUserDto, UpdateUserDto, UserResponseDto } from './dto/user.dto';

interface User {
  id: string;
  username: string;
  email: string;
  password: string;
  fullName?: string;
  createdAt: Date;
  updatedAt: Date;
}

@Injectable()
export class UsersService {
  private users: User[] = [
    {
      id: '1',
      username: 'admin',
      email: 'admin@example.com',
      password: 'password123',
      fullName: 'Administrator',
      createdAt: new Date('2024-01-01'),
      updatedAt: new Date('2024-01-01'),
    },
  ];

  async findAll(): Promise<UserResponseDto[]> {
    return this.users.map(user => this.toResponseDto(user));
  }

  async findOne(id: string): Promise<UserResponseDto> {
    const user = this.users.find(u => u.id === id);
    if (!user) {
      throw new NotFoundException(`ユーザーID ${id} が見つかりません`);
    }
    return this.toResponseDto(user);
  }

  async create(createUserDto: CreateUserDto): Promise<UserResponseDto> {
    // メールアドレスの重複チェック
    const existingUser = this.users.find(u => u.email === createUserDto.email);
    if (existingUser) {
      throw new ConflictException('このメールアドレスは既に使用されています');
    }

    // ユーザー名の重複チェック
    const existingUsername = this.users.find(u => u.username === createUserDto.username);
    if (existingUsername) {
      throw new ConflictException('このユーザー名は既に使用されています');
    }

    const newUser: User = {
      id: (this.users.length + 1).toString(),
      ...createUserDto,
      createdAt: new Date(),
      updatedAt: new Date(),
    };

    this.users.push(newUser);
    return this.toResponseDto(newUser);
  }

  async update(id: string, updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
    const userIndex = this.users.findIndex(u => u.id === id);
    if (userIndex === -1) {
      throw new NotFoundException(`ユーザーID ${id} が見つかりません`);
    }

    // 重複チェック(自分以外)
    if (updateUserDto.email) {
      const existingUser = this.users.find(u => u.email === updateUserDto.email && u.id !== id);
      if (existingUser) {
        throw new ConflictException('このメールアドレスは既に使用されています');
      }
    }

    this.users[userIndex] = {
      ...this.users[userIndex],
      ...updateUserDto,
      updatedAt: new Date(),
    };

    return this.toResponseDto(this.users[userIndex]);
  }

  async remove(id: string): Promise<void> {
    const userIndex = this.users.findIndex(u => u.id === id);
    if (userIndex === -1) {
      throw new NotFoundException(`ユーザーID ${id} が見つかりません`);
    }

    this.users.splice(userIndex, 1);
  }

  private toResponseDto(user: User): UserResponseDto {
    const { password, ...userResponse } = user;
    return userResponse;
  }
}

AWS SAMによるデプロイ設定

1. SAMテンプレート

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: NestJS API on AWS Lambda with API Gateway

Globals:
  Function:
    Timeout: 30
    MemorySize: 1024
    Runtime: nodejs18.x
    Environment:
      Variables:
        NODE_ENV: production
  Api:
    Cors:
      AllowMethods: "'GET,POST,PUT,PATCH,DELETE,OPTIONS'"
      AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
      AllowOrigin: "'*'"

Resources:
  NestJSApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: dist/
      Handler: lambda.handler
      Description: NestJS API Lambda Function
      Events:
        ProxyApiRoot:
          Type: Api
          Properties:
            RestApiId: !Ref NestJSApi
            Path: /
            Method: ANY
        ProxyApiGreedy:
          Type: Api
          Properties:
            RestApiId: !Ref NestJSApi
            Path: /{proxy+}
            Method: ANY

  NestJSApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: Prod
      Description: NestJS API Gateway
      Cors:
        AllowMethods: "'GET,POST,PUT,PATCH,DELETE,OPTIONS'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'*'"
      BinaryMediaTypes:
        - '*/*'
      EndpointConfiguration:
        Type: REGIONAL

Outputs:
  ApiGatewayUrl:
    Description: "API Gateway endpoint URL for Prod stage"
    Value: !Sub "https://${NestJSApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
    Export:
      Name: !Sub "${AWS::StackName}-ApiGatewayUrl"
  
  ApiDocumentationUrl:
    Description: "API Documentation URL (Swagger UI)"
    Value: !Sub "https://${NestJSApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/api"
    Export:
      Name: !Sub "${AWS::StackName}-ApiDocumentationUrl"

  HealthCheckUrl:
    Description: "Health Check endpoint URL"
    Value: !Sub "https://${NestJSApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/health"
    Export:
      Name: !Sub "${AWS::StackName}-HealthCheckUrl"

2. デプロイスクリプト

deploy.sh

#!/bin/bash

set -e

echo "🚀 Starting NestJS API deployment to AWS..."

# TypeScriptビルド
echo "Building NestJS application..."
npm run build

# package.jsonをdistフォルダにコピー
echo "Copying package files to dist folder..."
cp package.json dist/
cp package-lock.json dist/

# 本番用依存関係のインストール
echo "Installing production dependencies..."
cd dist
npm install --production --omit=dev --silent
cd ..

# SAMビルド
echo "Building SAM application..."
sam build

# デプロイ
echo "Deploying to AWS..."
sam deploy --resolve-s3 --no-confirm-changeset

echo "Deployment completed successfully!"

実装時のポイントと注意事項

1. Lambda特有の考慮事項

コールドスタート対策

let cachedServer: any;

async function bootstrap() {
  if (!cachedServer) {
    // サーバーインスタンスをキャッシュ
    cachedServer = configure({ app: expressApp });
  }
  return cachedServer;
}

インポート方法の注意

// ❌ 間違い
import express from 'express';
import serverlessExpress from '@vendia/serverless-express';

// ✅ 正しい
import * as express from 'express';
import { configure } from '@vendia/serverless-express';

2. CORS設定

API GatewayとNestJS両方でCORS設定が必要です。

NestJS側

app.enableCors({
  origin: '*',
  methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
  allowedHeaders: 'Content-Type,Authorization,X-Requested-With',
});

SAMテンプレート側

Cors:
  AllowMethods: "'GET,POST,PUT,PATCH,DELETE,OPTIONS'"
  AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
  AllowOrigin: "'*'"

3. エラーハンドリング

Lambda環境では適切なエラーハンドリングが重要です。

export const handler = async (event, context) => {
  try {
    const server = await bootstrap();
    return await server(event, context);
  } catch (error) {
    console.error('Error processing request:', error);
    
    return {
      statusCode: 500,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify({
        error: 'Internal Server Error',
        message: 'An error occurred while processing the request',
        timestamp: new Date().toISOString(),
      }),
    };
  }
};

デプロイと動作確認

1. デプロイ実行

# 実行権限を付与
chmod +x deploy.sh

# デプロイ実行
./deploy.sh

2. 動作確認

デプロイが完了すると、以下のURLが出力されます:

  • API Gateway URL: https://xxx.execute-api.ap-northeast-1.amazonaws.com/Prod/
  • Swagger UI: https://xxx.execute-api.ap-northeast-1.amazonaws.com/Prod/api
  • ヘルスチェック: https://xxx.execute-api.ap-northeast-1.amazonaws.com/Prod/health

3. APIテスト例

# ヘルスチェック
curl https://your-api-gateway-url/health

# ユーザー作成
curl -X POST https://your-api-gateway-url/users \
  -H "Content-Type: application/json" \
  -d '{
    "username": "testuser",
    "email": "test@example.com",
    "password": "password123",
    "fullName": "Test User"
  }'

# 商品一覧取得
curl https://your-api-gateway-url/products

# 注文作成
curl -X POST https://your-api-gateway-url/orders \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "1",
    "items": [{"productId": "1", "quantity": 2}],
    "shippingAddress": "東京都渋谷区...",
    "notes": "午前中配送希望"
  }'

まとめ

この記事では、NestJSを使用してAWS Lambda + API Gateway上で動作するサーバーレスAPIを構築する方法を詳しく解説しました。

学んだこと

  1. NestJSの基本概念

    • モジュール、コントローラー、サービス、DTOの役割
    • デコレータベースの開発手法
  2. AWS Lambda対応

    • serverless-expressを使用したExpress統合
    • コールドスタート対策
    • 適切なインポート方法
  3. API Gateway統合

    • CORS設定の重要性
    • プロキシ統合の設定方法
  4. 実践的な機能実装

    • バリデーション機能
    • エラーハンドリング
    • Swagger UI統合
    • 在庫管理を含むビジネスロジック

次のステップ

本記事で構築したAPIをさらに発展させるには:

  1. データベース統合

    • DynamoDB や RDS との連携
    • TypeORM や Prisma の導入
  2. 認証・認可

    • JWT トークン認証
    • AWS Cognito 統合
  3. テスト実装

    • ユニットテスト
    • 統合テスト
  4. CI/CD パイプライン

    • GitHub Actions
    • AWS CodePipeline
  5. 監視・ログ

    • CloudWatch メトリクス
    • X-Ray トレーシング

NestJSとAWSの組み合わせにより、スケーラブルで保守性の高いサーバーレスAPIを効率的に構築できることがお分かりいただけたと思います。ぜひ実際に手を動かして試してみてください!

参考リンク

遠藤 広也
(エンジニアブログ記事一覧)
(サバワク記事一覧)

アプリケーションサービス本部

2024年中途入社 アプリケーションサービス本部にてAWSを活用したアプリケーション開発に携わっています!
趣味はお酒とバンドです