はじめに
Node.jsでバックエンドを書いていると、プロジェクトが大きくなるにつれて「これ、設計どうする?」という壁にぶつかることがあると思います。Expressは自由度が高い反面、チームで開発すると人によって書き方がバラバラになりがちなんですよね。
そこで注目したいのが NestJS です。TypeScriptベースのNode.jsフレームワークで、GitHubスター数は73,000を超える人気プロジェクト。Angularからインスピレーションを受けた設計思想で、エンタープライズ級のアプリケーションを効率的に構築できます。
正直なところ、最初は「また新しいフレームワークか」と思っていたんですが、実際に使ってみると納得の設計でした。
NestJSの特徴・メリット
1. TypeScript完全対応
NestJSはTypeScriptでの開発を前提に設計されています。型安全性が担保されるので、大規模プロジェクトでもリファクタリングが怖くない。IDEの補完も効くし、開発体験がかなり良いです。
2. 依存性注入(DI)が標準装備
これが個人的には一番のポイント。DIコンテナが組み込まれているので、テスタビリティが格段に上がります。モックへの差し替えが簡単なので、ユニットテストが書きやすい。
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
}
3. モジュールベースのアーキテクチャ
機能ごとにモジュールを分割できるので、コードの見通しが良くなります。チーム開発でも「このモジュールは誰の担当」と分担しやすい。
4. Express / Fastify両対応
内部的にはExpressがデフォルトですが、Fastifyに切り替えることもできます。パフォーマンスを追求したい場合はFastifyという選択肢があるのは心強い。
5. 豊富なエコシステム
REST API、GraphQL、WebSocket、マイクロサービス、キューイングなど、だいたいのユースケースは公式モジュールでカバーされています。車輪の再発明をしなくて済むのは大きい。
インストール方法
NestJSにはCLIが用意されていて、プロジェクトの雛形を一発で作成できます。
# Nest CLIをグローバルインストール
npm install -g @nestjs/cli
# 新規プロジェクト作成
nest new my-project
パッケージマネージャーを聞かれるので、npm / yarn / pnpm から好きなものを選択。これだけで基本的なプロジェクト構成が出来上がります。
既存プロジェクトに追加する場合は、必要なパッケージを個別にインストール。
npm install @nestjs/core @nestjs/common @nestjs/platform-express reflect-metadata rxjs
基本的な使い方
プロジェクト構成
CLIで生成されるプロジェクトは以下のような構成になります。
src/
├── app.controller.ts # コントローラー
├── app.controller.spec.ts
├── app.module.ts # ルートモジュール
├── app.service.ts # サービス
└── main.ts # エントリーポイント
コントローラーの実装
HTTPリクエストを受け付けるコントローラーの例です。デコレータでルーティングを定義します。
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
findAll() {
return this.userService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.userService.findOne(id);
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
サービスの実装
ビジネスロジックはサービスに切り出します。@Injectable()デコレータを付けることでDIコンテナに登録されます。
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UserService {
private users = [];
findAll() {
return this.users;
}
findOne(id: string) {
const user = this.users.find(u => u.id === id);
if (!user) {
throw new NotFoundException(`User #${id} not found`);
}
return user;
}
create(createUserDto: CreateUserDto) {
const user = { id: Date.now().toString(), ...createUserDto };
this.users.push(user);
return user;
}
}
モジュールの定義
関連するコントローラーとサービスをモジュールにまとめます。
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService], // 他モジュールで使う場合
})
export class UserModule {}
開発サーバーの起動
npm run start:dev
ホットリロードが効くので、ファイルを保存すると自動で再起動されます。開発効率が良いですね。
実践的なユースケース
バリデーション
class-validatorとclass-transformerを使ったバリデーションが便利です。
npm install class-validator class-transformer
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
name: string;
@IsEmail()
email: string;
@MinLength(8)
password: string;
}
main.tsでValidationPipeを有効化すれば、自動でバリデーションが走ります。
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
認証(JWT)
@nestjs/passportと@nestjs/jwtを組み合わせてJWT認証を実装できます。
npm install @nestjs/passport @nestjs/jwt passport passport-jwt
Guardを使ってエンドポイントを保護。
import { UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
@Controller('profile')
export class ProfileController {
@UseGuards(JwtAuthGuard)
@Get()
getProfile(@Request() req) {
return req.user;
}
}
データベース連携(TypeORM)
TypeORMとの統合もスムーズです。
npm install @nestjs/typeorm typeorm pg
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
}
リポジトリパターンでデータアクセスを抽象化できるので、テストも書きやすくなります。
まとめ
NestJSは、Node.jsでしっかりとしたバックエンドを構築したいときの有力な選択肢です。
- TypeScript完全対応で型安全
- DIとモジュール構成で保守性が高い
- テストが書きやすい設計
- 公式モジュールが充実
個人的には、ある程度規模のあるプロジェクトではNestJS一択ですね。学習コストはExpressより高めですが、チーム開発での効率を考えると十分ペイすると思います。
最初はAngularっぽいデコレータの書き方に戸惑うかもしれませんが、慣れてしまえばむしろ快適。「Node.jsでちゃんとしたAPI作りたいんだよな」という方は、ぜひ試してみてください。
公式ドキュメントも充実しているので、まずはdocs.nestjs.comを眺めてみることをおすすめします。