気になる人向けの NestJS入門
この記事はNestJS気になるけど
自分はexpressで良いかなぁ・・・、honoで良いかなぁ・・・となっている人向けの記事です。
API開発完全初心者の方にはあまり向いていません。
自分もHonoやOakを使っていますが、NestJSのAngularに準拠した書き方が気になったので入門してみます。
セットアップ
NestJS公式が出しているCLIツールでセットアップをしてみます。
npm install -g @nestjs/cli
pnpm install -g @nestjs/cli
nest new sample-app
これでアプリを新規作成可能です。
この時点でもうAngularに似てますね。
コードを見て見る
ファイル構成はこのような感じです。
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
滅茶苦茶Angularです。
ちなみにStackBlitzを用いてテストしています。
使い方はこちら
そしてここが一番重要です。
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
ここではGETでリクエストが送信された時、
// app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
このクラスにある getHello
メソッドを実行するようにしています。
起動してみる
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
main.tsをエントリーポイントにして起動する用です。
app.listen([[port]])
見慣れた奴ですね
起動して、localhost:3000
を開きます。
ちゃんと表示できてます。
新しいエンドポイントを作る
サーバー側の時間を返すエンドポイントを作ってみます。
// app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
getTime(): string {
return new Date(Date.now()).toLocaleDateString();
}
}
今の時間を返す getTime
メソッドを作成しました。
これをルーティングしてみます。
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("time")
getTime(): string {
return this.appService.getTime()
}
}
しっかりルーティング出来てます。
HonoやExpressでも出来る機能を紹介します。(全てでは有りません)
その他
POSTメソッドや他のメソッドの利用
何となく察しているかも知れませんが、
// app.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("time")
getTime(): string {
return this.appService.getTime()
}
@Post("time-post")
getTimePost(): string {
return this.appService.getTime()
}
}
@Post
デコレーターでルーティング可能です。
しっかり取得できてます。
また他のmethodも利用可能です。
動的ルーティング
// app.controller.ts
import { Controller, Get, Post, Param } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("time")
getTime(): string {
return this.appService.getTime()
}
@Post("time-post")
getTimePost(): string {
return this.appService.getTime()
}
@Post('name/:param')
getPathParameters(@Param('param') param: string): string {
return `My Name is ${param}`;
}
}
この様に癖が無く、快適です。
basePath
クラスの @Controller
デコレーターの中に文字列を入れればOKです。
これにより /api
を基準としてアクセスできます。
物凄く簡単
ワイルドカード
これも動的ルーティングとほぼ同じです。
// app.controller.ts
import { Controller, Get, Post, Param } from '@nestjs/common';
import { AppService } from './app.service';
@Controller("api")
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("time")
getTime(): string {
return this.appService.getTime()
}
@Post("time-post")
getTimePost(): string {
return this.appService.getTime()
}
@Post('name/:param')
getPathParameters(@Param('param') param: string): string {
return `My Name is ${param}`;
}
@Post('i-am-*')
getGreet(): string {
return "hi!"
}
}
リクエストのオブジェクト (コンテキスト)
// app.controller.ts
import { Controller, Get, Post, Param, Req } from '@nestjs/common';
import { AppService } from './app.service';
@Controller("api")
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("time")
getTime(): string {
return this.appService.getTime()
}
@Post("time-post")
getTimePost(): string {
return this.appService.getTime()
}
@Post('name/:param')
getPathParameters(@Param('param') param: string): string {
return `My Name is ${param}`;
}
@Post('i-am-*')
getGreet(): string {
return "hi!"
}
@Post('ctx')
getCtx(@Req() req): string {
console.log(req)
return "hello!"
}
}
ただしリクエストのコンテキストを取得したい場合、普通は@Body
デコレーター等を使うのがエレガントです。
ルーティングファイル分割
ここでそろそろファイルが肥大化してきたので新しくコントローラーを作ります。
app.controller.ts
のようなファイルを新しく作り、app.module.ts
に登録するだけです。
// app2.controller.ts
import { Controller, Get, Post, Param, Req } from '@nestjs/common';
import { AppService } from './app.service';
@Controller("api2")
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppController as App2Controller } from './app2.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController, App2Controller],
providers: [AppService],
})
export class AppModule {}
これで完成です。
超簡単
Query
// app2.controller.ts
import { Controller, Get, Post, Query } from '@nestjs/common';
import { AppService } from './app.service';
@Controller("api2")
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("query")
getQuery(@Query("id") id): string {
return id;
}
}
@Query
の引数がパラメーターの名前です。
Body
// app2.controller.ts
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { AppService } from './app.service';
@Controller("api2")
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("query")
getQuery(@Query("id") id): string {
return id;
}
@Post("body")
getBody(@Body("id") id): string {
console.log(id);
return "hey body!";
}
}
レスポンスヘッダー
// app2.controller.ts
import { Body, Controller, Get, Post, Query, HttpCode, Header } from '@nestjs/common';
import { AppService } from './app.service';
@Controller("api2")
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@HttpCode(201)
@Header("Content-Type", "application/json")
getHello(): string {
return JSON.stringify({
"name": "amex2189"
});
}
@Get("query")
getQuery(@Query("id") id): string {
return id;
}
@Post("body")
getBody(@Body("id") id): string {
console.log(id)
return "hey body!"
}
}
エラーハンドリング
new HttpException('Forbidden', HttpStatus.FORBIDDEN)
等を返すことで可能です。
その他実装のヒント
パラメーター不足等のハンドリング
パラメーターが足りない場合等のハンドリングは PIPE と呼ばれる機能を使うと良いです。 (Angularにもある通りtransform関数の引数にクエリを入れて実行し、好きな形で返せます。)
ログイン機能
GUARD と呼ばれる機能でログインしてない場合、区別しハンドリングという事が可能です。
DATABASE
AUTH
関連記事
- NestJSのススメ ~Expressを超えてゆけ~
- NestJS公式ドキュメント翻訳
- NestJSでZodを使用した開発を考えてみる
- NestJS アドヴェントカレンダー
- NestJSでexpressを使う
最後に
基本的な物は以上です。
個人的にHonoやExpressとは違う堅牢性やAngularのようなファイル分割でロジックの分割が上手くできていてこれなら保守も楽そうだなと感じました。
Honoにも高性能なRoute機能が有りますが、これとはまた違った使用感です。
更にWebSocket対応や、ORMの対応がHonoよりも優れていると感じました。
最初の開発体験はHonoと比べると劣ってしまいますが、それも慣れていくにつれて改善されるものだと思います。
今回作ったAPIのソースはここに有ります。
ちなみに開発モードは
npm run start:dev
pnpm run start:dev
で可能です。
最後まで読んでいただきありがとうございます。
良ければハートを教えて頂けるとありがたいです。
Discussion