【実践】NestJS で REST API を構築する基本的な流れ

Node.js での API 開発において、大規模なアプリケーションを構築する際に構造化された設計が求められています。
NestJS は Angular にインスパイアされた Node.js フレームワークで、TypeScript を標準サポートし、依存性注入やモジュール設計により保守性の高い REST API を構築できます。本記事では、NestJS を使って実際に REST API を構築する流れを、初心者の方にもわかりやすく解説いたします。
背景
NestJS とは
NestJS は 2017 年にリリースされた比較的新しい Node.js フレームワークです。Angular の設計哲学を Node.js 環境に持ち込み、エンタープライズレベルのサーバーサイドアプリケーション開発を効率化することを目的としています。
mermaidflowchart TB
angular[Angular] -->|設計思想| nestjs[NestJS]
express[Express.js] -->|基盤| nestjs
typescript[TypeScript] -->|標準サポート| nestjs
decorator[デコレータ] -->|機能実装| nestjs
nestjs --> api[スケーラブルなAPI]
NestJS の特徴として、TypeScript ファーストの開発体験、豊富なデコレータによる宣言的な記述、そして強力な依存性注入システムが挙げられます。
REST API 開発における NestJS の位置づけ
現代の Web API 開発では、単純な CRUD 操作を超えて、認証・認可、バリデーション、ドキュメント化、テストなど多岐にわたる要件が求められます。NestJS はこれらの機能を標準で提供し、開発者がビジネスロジックに集中できる環境を提供しています。
# | 機能分野 | Express.js | NestJS |
---|---|---|---|
1 | 基本的なルーティング | 標準機能 | 豊富なデコレータで宣言的 |
2 | 認証・認可 | 追加パッケージが必要 | Passport 統合、Guard システム |
3 | バリデーション | 手動実装 | class-validator との統合 |
4 | API ドキュメント | 別途設定 | Swagger 自動生成 |
5 | テスト | Jest など別途設定 | テスト機能内蔵 |
従来の Node.js 開発との比較
Express.js を使った従来の開発では、プロジェクトが成長するにつれて以下のような課題が浮上してきました。
mermaidflowchart LR
express[Express.js] --> issues[課題]
issues --> structure[構造化の困難]
issues --> test[テストの複雑化]
issues --> maintain[保守性の低下]
nestjs[NestJS] --> solutions[解決策]
solutions --> modules[モジュール設計]
solutions --> di[依存性注入]
solutions --> builtin[豊富な標準機能]
従来のアプローチでは、開発者が独自にファイル構造を決定し、依存関係を管理する必要がありました。一方、NestJS は規約に基づいた構造化されたアプローチを提供し、チーム開発での一貫性を保ちます。
課題
Express.js での開発の限界
Express.js は軽量で柔軟性が高い反面、大規模なアプリケーション開発において以下のような制約が生じがちです。
まず、ファイル構造の統一性に関する課題があります。Express.js では特定のプロジェクト構造を強制しないため、チームメンバーによって実装方法が異なり、コードの一貫性を保つのが困難になります。
javascript// 従来のExpress.jsでの実装例
app.get('/users', (req, res) => {
// ビジネスロジック、バリデーション、エラーハンドリングがすべて混在
if (!req.query.page) {
return res
.status(400)
.json({ error: 'Page parameter required' });
}
UserService.getUsers(req.query.page)
.then((users) => res.json(users))
.catch((err) =>
res.status(500).json({ error: err.message })
);
});
このように、ルーティング、バリデーション、ビジネスロジックが一箇所に集約され、保守性が低下します。
スケーラブルな API 設計の難しさ
API が成長するにつれて、以下のような設計上の問題が顕在化します。
依存関係の管理が複雑になることです。サービス間の依存関係を手動で管理する必要があり、テストやリファクタリングが困難になります。
javascript// 手動での依存関係管理の例
const userService = new UserService(
new DatabaseConnection(),
new Logger()
);
const authService = new AuthService(
userService,
new JWTProvider()
);
横断的関心事の実装も課題となります。認証、ログ、バリデーションなどの機能を各エンドポイントで個別に実装する必要があり、コードの重複が発生します。
TypeScript との統合課題
Express.js で TypeScript を使用する場合、以下のような問題が生じることがあります。
型安全性の部分的な確保しかできません。リクエスト・レスポンスの型定義や、ミドルウェアでの型の連携が不完全になりがちです。
typescript// Express.jsでのTypeScript使用時の型安全性の課題
app.get('/users/:id', (req: Request, res: Response) => {
// req.params.idの型が string | undefined で不確実
// req.bodyの型情報が不十分
const userId = parseInt(req.params.id); // 型変換が必要
});
デコレータの活用が限定的で、メタデータベースの機能実装が困難になります。
解決策
NestJS の特徴とメリット
NestJS は前述の課題を以下の特徴により解決します。
アーキテクチャの統一により、プロジェクト全体で一貫した構造を提供します。コントローラー、サービス、モジュールという明確な役割分担により、保守性の高いコードが実現できます。
mermaidflowchart TD
module[Module] --> controller[Controller]
module --> service[Service]
module --> provider[Provider]
controller -->|依存性注入| service
service -->|ビジネスロジック| db[(Database)]
controller -->|HTTP レスポンス| client[Client]
TypeScript ファーストの設計により、型安全性が標準で保証されます。リクエスト・レスポンスの型定義から、依存性注入まで、すべて型チェックの恩恵を受けられます。
依存性注入とモジュール設計
NestJS の最大の特徴の一つが**依存性注入(DI: Dependency Injection)**です。これにより、オブジェクト間の依存関係をフレームワークが自動で解決し、テスタブルで疎結合なコードが実現できます。
typescript// NestJSでの依存性注入の例
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService, // 自動で注入される
private readonly authService: AuthService // 自動で注入される
) {}
}
モジュールシステムにより、機能ごとにコードを整理し、再利用可能なコンポーネントを作成できます。
mermaidflowchart TB
app[AppModule] --> users[UsersModule]
app --> auth[AuthModule]
app --> common[CommonModule]
users --> usersController[UsersController]
users --> usersService[UsersService]
auth --> authController[AuthController]
auth --> authService[AuthService]
デコレータベースの開発手法
NestJS はデコレータを活用した宣言的な開発スタイルを採用しています。これにより、コードの可読性が向上し、横断的関心事の実装が簡潔になります。
ルーティングデコレータにより、HTTP メソッドとパスを明確に定義できます。
typescript@Controller('users')
export class UsersController {
@Get() // GET /users
@Post() // POST /users
@Put(':id') // PUT /users/:id
@Delete(':id') // DELETE /users/:id
}
バリデーションデコレータにより、入力値の検証を宣言的に記述できます。
typescriptexport class CreateUserDto {
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@MinLength(8)
password: string;
}
具体例
環境構築とプロジェクト初期化
NestJS プロジェクトの作成から始めましょう。まず、NestJS CLI をグローバルにインストールします。
bash# NestJS CLIのインストール
yarn global add @nestjs/cli
新しいプロジェクトを作成します。CLI が対話形式でセットアップを進めてくれます。
bash# プロジェクト作成
nest new my-api-project
# プロジェクトディレクトリに移動
cd my-api-project
プロジェクトの基本構造を確認しましょう。
textmy-api-project/
├── src/
│ ├── app.controller.ts # メインコントローラー
│ ├── app.service.ts # メインサービス
│ ├── app.module.ts # ルートモジュール
│ └── main.ts # アプリケーションエントリーポイント
├── test/ # テストファイル
├── package.json
└── tsconfig.json
開発サーバーを起動し、動作確認を行います。
bash# 開発サーバー起動
yarn start:dev
ブラウザで http://localhost:3000
にアクセスすると、"Hello World!" が表示されます。これで基本的な環境構築が完了です。
Controller の作成
実際のユーザー管理 API を作成しましょう。まず、ユーザー関連のリソースを生成します。
bash# ユーザーモジュール、コントローラー、サービスを一括生成
nest generate resource users
CLI が対話形式で以下を確認します:
- Transport layer: REST API を選択
- CRUD entry points: Yes を選択
生成された users.controller.ts
を確認します。
typescript// src/users/users.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
@Controller('users')
export class UsersController {
constructor(
private readonly usersService: UsersService
) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}
}
各デコレータの役割を理解しましょう。
# | デコレータ | 説明 | HTTP メソッド |
---|---|---|---|
1 | @Controller('users') | クラス全体のベースパスを設定 | - |
2 | @Post() | POST リクエストを処理 | POST |
3 | @Get() | GET リクエストを処理 | GET |
4 | @Get(':id') | パラメータ付き GET リクエスト | GET |
5 | @Body() | リクエストボディを取得 | - |
6 | @Param('id') | パスパラメータを取得 | - |
Service の実装
ビジネスロジックを担当するサービスを実装します。生成された users.service.ts
を編集しましょう。
typescript// src/users/users.service.ts
import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
// ユーザーエンティティの型定義
export interface User {
id: number;
email: string;
name: string;
createdAt: Date;
}
@Injectable()
export class UsersService {
private users: User[] = []; // 仮のデータストア
private currentId = 1;
create(createUserDto: CreateUserDto): User {
const newUser: User = {
id: this.currentId++,
...createUserDto,
createdAt: new Date(),
};
this.users.push(newUser);
return newUser;
}
findAll(): User[] {
return this.users;
}
findOne(id: number): User {
const user = this.users.find((user) => user.id === id);
if (!user) {
throw new NotFoundException(
`User with ID ${id} not found`
);
}
return user;
}
}
@Injectable()
デコレータにより、このサービスが依存性注入の対象となり、コントローラーで自動的に注入されます。
DTO とバリデーション
データ転送オブジェクト(DTO)とバリデーション機能を実装します。まず、必要なパッケージをインストールします。
bash# バリデーション用パッケージのインストール
yarn add class-validator class-transformer
create-user.dto.ts
を編集してバリデーションルールを追加します。
typescript// src/users/dto/create-user.dto.ts
import {
IsEmail,
IsNotEmpty,
IsString,
MinLength,
MaxLength,
} from 'class-validator';
export class CreateUserDto {
@IsEmail(
{},
{ message: '有効なメールアドレスを入力してください' }
)
@IsNotEmpty({ message: 'メールアドレスは必須です' })
email: string;
@IsString({ message: '名前は文字列である必要があります' })
@IsNotEmpty({ message: '名前は必須です' })
@MinLength(2, {
message: '名前は2文字以上である必要があります',
})
@MaxLength(50, {
message: '名前は50文字以下である必要があります',
})
name: string;
}
グローバルバリデーションパイプを設定します。
typescript// src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// グローバルバリデーションパイプの設定
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // DTOで定義されていないプロパティを除外
forbidNonWhitelisted: true, // 不正なプロパティがある場合エラー
transform: true, // 型変換を自動実行
})
);
await app.listen(3000);
}
bootstrap();
データベース連携(TypeORM)
実際のデータベースと連携しましょう。TypeORM を使用して PostgreSQL に接続します。
bash# TypeORMとデータベース関連パッケージのインストール
yarn add @nestjs/typeorm typeorm pg
yarn add -D @types/pg
ユーザーエンティティを作成します。
typescript// src/users/entities/user.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
name: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
データベース接続を設定します。
typescript// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'your-username',
password: 'your-password',
database: 'your-database',
autoLoadEntities: true,
synchronize: true, // 本番環境では false に設定
}),
UsersModule,
],
})
export class AppModule {}
ユーザーモジュールでエンティティを登録します。
typescript// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
サービスをデータベース操作に対応させます。
typescript// src/users/users.service.ts(データベース対応版)
import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>
) {}
async create(
createUserDto: CreateUserDto
): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return await this.usersRepository.save(user);
}
async findAll(): Promise<User[]> {
return await this.usersRepository.find();
}
async findOne(id: number): Promise<User> {
const user = await this.usersRepository.findOne({
where: { id },
});
if (!user) {
throw new NotFoundException(
`User with ID ${id} not found`
);
}
return user;
}
}
認証機能の実装
JWT を使った認証機能を実装します。まず、必要なパッケージをインストールします。
bash# 認証関連パッケージのインストール
yarn add @nestjs/jwt @nestjs/passport passport passport-jwt bcryptjs
yarn add -D @types/passport-jwt @types/bcryptjs
認証モジュールを生成します。
bash# 認証モジュール生成
nest generate module auth
nest generate service auth
nest generate controller auth
JWT 設定を追加します。
typescript// src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: 'your-secret-key', // 環境変数から読み込むことを推奨
signOptions: { expiresIn: '24h' },
}),
],
providers: [AuthService, JwtStrategy],
controllers: [AuthController],
})
export class AuthModule {}
JWT ストラテジーを実装します。
typescript// src/auth/jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(
Strategy
) {
constructor() {
super({
jwtFromRequest:
ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'your-secret-key',
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
認証が必要なエンドポイントに Guard を適用します。
typescript// src/users/users.controller.ts(認証対応版)
import {
Controller,
Get,
Post,
Body,
UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { UsersService } from './users.service';
@Controller('users')
@UseGuards(JwtAuthGuard) // 全エンドポイントに認証を適用
export class UsersController {
constructor(
private readonly usersService: UsersService
) {}
@Get()
findAll() {
return this.usersService.findAll();
}
}
まとめ
NestJS を使った REST API 開発は、従来の Express.js ベースの開発と比較して以下のメリットがあります。
まず、構造化されたアーキテクチャにより、大規模なプロジェクトでも一貫性を保った開発が可能です。モジュール設計と依存性注入により、テストしやすく保守しやすいコードが実現できます。
TypeScript ファーストの設計により、型安全性が保証され、開発時のエラーを早期に発見できます。また、豊富な標準機能により、認証、バリデーション、データベース操作などの実装が大幅に簡素化されます。
デコレータベースの開発手法により、コードの可読性が向上し、横断的関心事の実装が宣言的に記述できます。これにより、ビジネスロジックに集中した開発が可能になります。
NestJS は学習コストが高い面もありますが、チーム開発や長期的な保守性を考慮した場合、その投資に見合う価値を提供してくれるフレームワークです。
今回紹介した基本的な構築方法をベースに、実際のプロジェクトでの要件に合わせてカスタマイズしていくことで、スケーラブルで保守しやすい API を構築できるでしょう。
関連リンク
- article
【実践】NestJS で REST API を構築する基本的な流れ
- article
NestJS でのモジュール設計パターン:アプリをスケーラブルに保つ方法
- article
【入門】NestJS とは?初心者が最初に知っておくべき基本概念と特徴
- article
Prisma と NestJS を組み合わせたモダン API 設計
- article
TypeScript で学ぶミドルウェア設計パターン:Express・NestJS の応用例
- article
【対処法】Next.js開発で発生するハイドレーションエラーの原因と解決策
- article
Python で始める自動化:ファイル操作・定期実行・スクレイピングの実践
- article
生成 AI 時代の新常識!GPT-5 のセキュリティ・倫理・安全設計の最新動向
- article
【実践】NestJS で REST API を構築する基本的な流れ
- article
TypeScript × GitHub Copilot:型情報を活かした高精度コーディング
- article
Motion(旧 Framer Motion)Variants 完全攻略:staggerChildren・when で複雑アニメを整理する
- article
JavaScript のオブジェクト操作まとめ:Object.keys/entries/values の使い方
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- blog
失敗を称賛する文化はどう作る?アジャイルな組織へ生まれ変わるための第一歩
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来