2022/03/12
はじめに
入門の次は,REST API をつくる.
今回は,DB との接続は省き,エンドポイントおよび処理の記述方法のみ記載する.
REST API の動作確認のためには,postman がオススメである.
REST API のための Module 構成
REST API の Module は,大きくわけて,以下のような構成になる.
TodoController は,外部(フロントエンド)との接点になる部分で,エンドポイントの指定を担い,ここには処理の実態は書かない様にすべきである.ここで,Controller に処理を書いてもアプリケーションは問題なく動作するが,コードの粗結合性や再利用性の観点から望ましくない.コードの粗結合という観点では,続いて紹介する GraphQL への移行においても重要な意味を持つ.
TodoService は,Todo に対する処理の実態を実装するためのものであり,これらは基本的には TodoController から呼ばれるために実装する.
Module を追加する
TODO を管理する Module を作る.
NestJS における Module は,特定の目的に対する機能の集合体のようなもので,Module は主に controller,service,resolve から構成される.
自分でファイル生成を行ってもよいが,NestCLI を使うと楽に生成可能.
今回は,TODO の REST API を作るので,todo モジュールを作る.
$ nest g mo todo
ここで,g
はgenerate
のショートハンドであり,mo
はmodule
のショートハンドである.
g
で生成できる要素はschematics
から提供され,nest g --help
から確認できる.
生成されるファイルは以下のようになっており,空っぽの Module が生成されていることが確認できる.
import { Module } from '@nestjs/common';
@Module({})
export class TodoModule {}
また,NestCLI による生成で非常に便利なのが,勝手に Module の登録を行ってくれる点であり,src/app.module.ts
に変更が加えられている.
import { Module } from '@nestjs/common';
import { TodoModule } from './todo/todo.module';
@Module({
imports: [TodoModule],
controllers: [],
providers: [],
})
export class AppModule {}
Controller の追加
controller とは,REST API のエンドポイントを設定するための役割を担い,REST API を作るために必要となる.
$ nest g controller todo
import { Controller } from '@nestjs/common';
@Controller('todo')
export class TodoController {}
todo.controller.ts
には,@Controller()
が使われており,これを Module に登録することで REST API にアクセスすることができる.
CLI で生成した場合,以下のように自動で Controller を追加してくれる.
import { Module } from '@nestjs/common';
import { TodoController } from './todo.controller';
@Module({
controllers: [TodoController],
})
export class TodoModule {}
ここまでで,REST API の準備が完了.
Get メソッドをつくる
シンプル
src/todo/todo.controller.ts
内に記述を加えると,/todo/*
のエンドポイントに API を作ることができる.ここで,/todo
内になるのは,@Controller('todo')
のように'todo'
が指定されているためであり,'hoge'
とすれば/hoge/*
のエンドポイントとなる.
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/hoge
→This is hoge
http://localhost:3000/todo/fuga
→This is fuga
id のみ受け取る
上の方法では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()
メソッドでペイロードを受け取る方法を記す.
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 を使う.
$ nest g mo service todo
これまで同様に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
を別ファイルに分割する.
export class CreateTodoDto {
title: string;
description: string;
}
生成されたtodo.service.ts
を以下のように修正する.
ここで,todos
は,暫定的に作成したものであり,本来はこれは DB で運用するべき点である.
todo.service.ts
のメソッドには,todo.controller.ts
から呼び出すための機能群であり,基本的にはTodoModule
内で扱われるが,これらは特定の設定によってtodo
モジュール以外のモジュールからも実行できるように設定できる.
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 から呼び出すためには,以下のような記述を加える.
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 との接続を書きたい.