ys memos

Blog

NestJS 入門コース2 REST API


nestjs

2022/03/12

前記事

次記事


入門の次は,REST API をつくる.

今回は,DB との接続は省き,エンドポイントおよび処理の記述方法のみ記載する.

REST API の動作確認のためには,postman がオススメである.


REST API の Module は,大きくわけて,以下のような構成になる.

TodoController は,外部(フロントエンド)との接点になる部分で,エンドポイントの指定を担い,ここには処理の実態は書かない様にすべきである.ここで,Controller に処理を書いてもアプリケーションは問題なく動作するが,コードの粗結合性や再利用性の観点から望ましくない.コードの粗結合という観点では,続いて紹介する GraphQL への移行においても重要な意味を持つ.

TodoService は,Todo に対する処理の実態を実装するためのものであり,これらは基本的には TodoController から呼ばれるために実装する.

graph TB; subgraph TodoModule id1(TodoController); id2(TodoService); id1-->id2; end

TODO を管理する Module を作る.

NestJS における Module は,特定の目的に対する機能の集合体のようなもので,Module は主に controller,service,resolve から構成される.

自分でファイル生成を行ってもよいが,NestCLI を使うと楽に生成可能.

今回は,TODO の REST API を作るので,todo モジュールを作る.

command
$ nest g mo todo

ここで,ggenerateのショートハンドであり,momoduleのショートハンドである.

gで生成できる要素はschematicsから提供され,nest g --helpから確認できる.

生成されるファイルは以下のようになっており,空っぽの Module が生成されていることが確認できる.

src/todo/todo.module.ts
import { Module } from '@nestjs/common';

@Module({})
export class TodoModule {}

また,NestCLI による生成で非常に便利なのが,勝手に Module の登録を行ってくれる点であり,src/app.module.tsに変更が加えられている.

src/app.module.ts
import { Module } from '@nestjs/common';
import { TodoModule } from './todo/todo.module';

@Module({
  imports: [TodoModule],
  controllers: [],
  providers: [],
})
export class AppModule {}

controller とは,REST API のエンドポイントを設定するための役割を担い,REST API を作るために必要となる.

command
$ nest g controller todo
src/todo/todo.controller.ts
import { Controller } from '@nestjs/common';

@Controller('todo')
export class TodoController {}

todo.controller.tsには,@Controller()が使われており,これを Module に登録することで REST API にアクセスすることができる.

CLI で生成した場合,以下のように自動で Controller を追加してくれる.

src/todo/todo.module.ts
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';

@Module({
  controllers: [TodoController],
})
export class TodoModule {}

ここまでで,REST API の準備が完了.



src/todo/todo.controller.ts内に記述を加えると,/todo/*のエンドポイントに API を作ることができる.ここで,/todo内になるのは,@Controller('todo')のように'todo'が指定されているためであり,'hoge'とすれば/hoge/*のエンドポイントとなる.

src/todo/todo.module.ts
import { Controller, Get } from '@nestjs/common';

@Controller('todo')
export class TodoController {
  @Get()
  test() {
    return 'This is todo';
  }
}

ここで,ブラウザを使ってhttp://localhost:3000/todoへアクセスするとThis is todoという文字列が返ってくる.test()というメソッドは,どんな名前でも関係ない.


変更内容
-  @Get()
-  test() {
-    return 'This is todo';
-  }
+  @Get('hoge')
+  hoge() {
+    return 'This is hoge';
+  }

http://localhost:3000/todo/hogeへアクセスするとThis is hogeが返される.

このように,@Get()に文字列を与えることで,/todoに接続してエンドポイントを指定することができる.


:idのようにすることで可変エンドポイントを作成することが出来る.

@Param()によって,URL から文字列を受け取ることが出来る.

変更内容
-  @Get('hoge')
-  hoge() {
-    return 'This is hoge';
-  }
+  @Get(':id')
+  hoge(@Param() param) {
+    console.log(param);
+    return `This is ${param.id}`;
+  }

http://localhost:3000/todo/hogeThis is hoge http://localhost:3000/todo/fugaThis is fuga

上の方法ではparamsはオブジェクトで受け取っており,idを取り出すためにparam.idへアクセスする必要がある.しかし,単一の可変文字列であれば,idのみ受け取りたい.そこで,以下のようにすると,idを直接受け取ることが出来る.

先程は型指定を省いたが,このように受け取ったほうが型が扱いやすい.

変更内容
-  @Get(':id')
-  hoge(@Param() param) {
-    console.log(param);
-    return `This is ${param.id}`;
-  }
+  @Get(':id')
+  hoge(@Param('id') id: string) {
+    return `This is ${id}`;
+  }

以下のようにすると,ネストされた可変エンドポイントが指定できる.また,@Param() paramとすると,両者を持つオブジェクトとして受け取ることも可能であるため,用途に応じて使い分けるのが良いだろう.

変更内容
-  @Get(':id')
-  hoge(@Param('id') id: string) {
-    return `This is ${id}`;
-  }
+  @Get(':id/:ad')
+  hoge(@Param('id') id: string, @Param('ad') ad: string) {
+    return `This is ${id}/${ad}`;
+  }
POST でペイロードを受け取る

@Get()とすると GET メソッドを受け取ることができるが,これと同様に@Post()/ @Post()/ @Put()/ @Delete()/ @Patch()/ @Options()/ @Head()などのメソッドも提供されているとのこと.

ここでは@Post()メソッドでペイロードを受け取る方法を記す.

src/todo.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';

export class CreateTodoDto {
  title: string;
  description: string;
}

@Controller('todo')
export class TodoController {
  @Get()
  getTodo(@Body() createTodoDto: CreateTodoDto) {
    console.log(createTodoDto);
    return 'This is todo';
  }

  @Post()
  createTodo(@Body() createTodoDto: CreateTodoDto) {
    console.log(createTodoDto);
    return 'This is todo';
  }
}

CreateTodoDtoは,TODO を作成するための入力を表す型である.

また,@Body()によってリクエストのペイロードを受け取ることができ,上述した@Param()同様に,文字列を入れ@Body('title')のようにすると,titleのみ受け取ることも可能である.

個人的には,ペイロードには複数の KeyVal を含めることが多いので,まとめて受け取るのがオススメであり,その際に DTO (Data Transfer Object)というものを使う.これは,Frontend/Controller 間などの異なるレイヤー間でデータを送るための Object である.


NestJS において,エンドポイントの設定は Controller に記述するのがこれまでの紹介であったが,ここでは処理の実態を記述する方法を紹介する.

Controller にエンドポイントに対する処理を記述すること自体は問題ないが,NestJS においては Service に処理の実態を書くことになっている.このルールを守ることで,(1)コード全体の見通しの良さ,(2)機能毎で記述場所の分離,(3)コードの再利用性の向上,(4)GraphQL への移行の容易性,の4点の恩恵が得られる.

service を作成する際も,NestCLI を使う.

command
$ nest g mo service todo

これまで同様にsrc/todo/todo.module.tsは自動で更新される.

src/todo/todo.module.ts
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
import { TodoService } from './todo.service';

@Module({
  controllers: [TodoController],
  // module内で使えるServiceを登録
  providers: [TodoService],
})
export class TodoModule {}

まずは,先程のCreateTodoDtoを別ファイルに分割する.

src/todo/create-todo.dto.ts
export class CreateTodoDto {
  title: string;
  description: string;
}

生成されたtodo.service.tsを以下のように修正する.

ここで,todosは,暫定的に作成したものであり,本来はこれは DB で運用するべき点である.

todo.service.tsのメソッドには,todo.controller.tsから呼び出すための機能群であり,基本的にはTodoModule内で扱われるが,これらは特定の設定によってtodoモジュール以外のモジュールからも実行できるように設定できる.

src/todo/todo.service.ts
import { Injectable } from '@nestjs/common';
import { CreateTodoDto } from './create-todo.dto';

// 簡単のため同一ファイルに記載しているが,ファイル分割したほうが便利.
export interface Todo {
  title: string;
  description: string;
}

// 簡単のため同一ファイルに記載しているが,ファイル分割したほうが便利.
export interface Todos {
  [id: string]: Todo;
}

@Injectable()
export class TodoService {
  private todos: Todos = {};

  // todoをIDにより取得するためのメソッド
  findOne(id: string): Todo {
    return this.todos[id];
  }

  // todoを作成するメソッド.
  createTodo(createTodoDto: CreateTodoDto): Todo {
    this.todos[Object.keys(this.todos).length] = createTodoDto;
    return createTodoDto;
  }
}

実際にtodo.service.tsのメソッドを Controller から呼び出すためには,以下のような記述を加える.

src/todo/todo.controller.ts
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { TodoService } from './todo.service';
import { CreateTodoDto } from './create-todo.dto';

@Controller('todo')
export class TodoController {
  // moduleのprovidersに含まれているServiceをメソッドとして扱うことが出来る
  constructor(private readonly todoService: TodoService) {}

  @Get(':id')
  findOne(@Param('id') id: string) {
    // TodoServiceのpublicメソッドをこのように呼び出すことが出来る
    return this.todoService.findOne(id);
  }

  @Post()
  createTodo(@Body() createTodoDto: CreateTodoDto) {
    return this.todoService.createTodo(createTodoDto);
  }
}

ここで,localhost:3000/todo{title:"test",description:"test task"}を POST すると TODO が追加され,localhost:3000/todo/0に GET するとこのデータを取得できる.


次回は DB との接続を書きたい.


関連タグを探す