NestJSを1年間使ってみての感想
はじめに
今年NestJSを使用してバックエンド開発を行ったのでその感想と簡単にNestJSについて記事にしました。開発したものは主に下記になります。1年間NestJSと共に駆け抜けました。
- 各種API
- バッチ処理
- SQSを使った非同期処理
私は都内でフリーランスのエンジニアをしています。
プロフィールはこちら。
NestJSについて簡単に
ChatGPTに聞いてみたら下記のように説明が返ってきました。
NestJSは、Node.jsをベースとしたサーバーサイドアプリケーションフレームワークです。TypeScriptで書かれており、モジュール性、スケーラビリティ、拡張性を備えた設計が特徴です。
expressやFastifyを内部で使用しており、軽量JSフレームワークを高機能にカスタムしたフレームワークという印象です。
公式のサイトはこちら
モジュールベース
NestJSの大きな特徴としてモジュールで各機能を実装します。
@Module、@Controller、@Injectableなどのデコレーターを各クラスに記述することでディレクトリ構成に囚われず機能を実装できます。
モジュール間で横断して使用したい場合呼び出し元のモジュールでexports、呼び出し先のモジュールではimportsに指定することで利用可能です。
※以下は一例です。
@Module({
imports: [PrismaModule],
providers: [TestRepository, TestService],
controllers: [TestController],
})
export class TestModule {}
@Controller('test')
export class TestController {
constructor(private readonly testService: TestService) {}
}
@Injectable()
export class TestService {
constructor(private readonly testRepository: TestRepository) {}
async create(test: CreateRequestTestDto) {
return await this.testRepository.create(test).then((result) => {
return plainToInstance(CreateTestResponseDto, result, { excludeExtraneousValues: true })
})
}
}
個人的には下記のような構成がしっくりきています。
├── infrastructure // DB接続やRedisへの接続するモジュール
│ └── prisma
│ ├── prisma.module.ts
│ └── prisma.service.ts
├── main.ts
├── middleware // フィルター処理やリポジトリなど
│ ├── filters
│ │ └── http-exception.filter.ts
│ └── repositories
│ └── test.repository.ts
└── modules // ドメイン単位のモジュール
└── test
├── app.module.ts
├── dto.ts
│ └── test.dto.ts
├── test.contoller.ts
├── test.module.ts
├── test.service.spec.ts
└── test.service.ts
APIドキュメントについて
API仕様書は多くのプロジェクトでOpenAPI(旧Swagger)を使用しているかと思いますが、
@nestjs/swaggerというnpmが用意されているのでデコレーターで簡単に実装可能です。
各コントローラーにてAPIの説明やレスポンスを定義しmain.tsのbootstrapにてxmlに出力するようにすれば開発しながらAPI仕様書を作成することができます。
ホットリロードされたタイミングでxmlが上書きされます。
実装例
@Post()
@ApiOperation({
operationId: 'test-create',
summary: 'テスト作成',
description: 'テスト作成APIの説明',
})
@ApiResponse({
status: 200,
type: CreateTestResponseDto,
})
@ApiOkResponse({
type: CreateTestResponseDto,
})
async create(@Body() body: CreateRequestTestDto): Promise<CreateTestResponseDto> {
return await this.testService.create(body)
}
@Get(':id')
@ApiOperation({
operationId: 'test-get',
summary: 'テスト取得',
description: 'テスト取得APIの説明',
})
@ApiOkResponse({
type: GetTestResponseDto,
})
@ApiBearerAuth()
async get(@Param('id') id: number): Promise<GetTestResponseDto> {
const result = await this.testService.findById(id)
return plainToInstance(GetTestResponseDto, result, { excludeExtraneousValues: true })
}
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.useGlobalFilters(new BadRequestExceptionFilter(), new NotFoundExceptionFilter(), new ForbiddenExceptionFilter())
const isDevelopment = process.env.NODE_ENV !== 'production'
if (isDevelopment) {
// Swaggerの設定
const config = new DocumentBuilder()
.setTitle('アプリケーション名')
.setDescription('APIの説明文')
.setVersion('1.0')
.addBearerAuth()
.addTag('APIタグ')
.build()
const document = SwaggerModule.createDocument(app, config)
// Swagger UIの設定
SwaggerModule.setup('api-docs', app, document)
// YAMLファイルとしてAPI仕様書を出力
const yamlString = dump(document, { skipInvalid: true })
writeFileSync('./swagger.yaml', yamlString, 'utf8')
}
/api-docsにアクセスするとこのような形で表示されます。
NestJSを使用しての感想
- 良い点
- デコレーターを使用して機能を定義するのでディレクトリ構成のカスタムが容易
- Cronもデコレーターで記述可能なのでバッチ処理の実装も容易
- 各種汎用機能(Prisma、Redis、Slack)をモジュール化することで横断的に各サービス層で使用可能。
- OpenAPIの定義をデコレーターベースで記述できるので比較的簡単にAPI仕様書を実装可能。
- 悪い点
- モジュール分割が複雑になってくると依存関係が複雑になる
最後に
今まではLaravelを使って開発することが多かったですが、NestJSは個人的にLaravelを超える使いやすさでした。FullTypeScriptでフロントエンドとバックエンドを開発するので言語のスイッチコストもなく開発が可能なのでフルスタックエンジニアとは相性が良いなと思いました。
個人的にバックエンドをTypeScriptで開発するプロジェクトが増えて欲しいと思っております。
Discussion