JavaScript
NestJS
TypeScript
public
NestJS を使う
NestJS は Web フレームワークである。
インストール
公式の方法は以下。
First steps | NestJS - A progressive Node.js framework
$ npm i -g @nestjs/cli
$ nest new project-name
nest コマンドをインストールしない場合は以下。
$ mkdir { プロジェクト名}
$ cd { プロジェクト名}
$ npm i @nestjs/cli --no-save
$ npx nest new { プロジェクト名} --directory= ./
Controller, Service, Module
Controller はリクエストを受信して、クライアントにレスポンスを返す。
Service はビジネスロジックを定義する。
Module は Controller と Service を紐づけて NestJS に登録する。
Todo API を作る
リソースを作る
npx nest g resourceでController、Service、Moduleを作ってくれる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 $ npx nest g resource
? What name would you like to use for this resource ( plural, e.g., "users" ) ? todos
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/todos/todos.controller.spec.ts ( 566 bytes)
CREATE src/todos/todos.controller.ts ( 894 bytes)
CREATE src/todos/todos.module.ts ( 248 bytes)
CREATE src/todos/todos.service.spec.ts ( 453 bytes)
CREATE src/todos/todos.service.ts ( 609 bytes)
CREATE src/todos/dto/create-todo.dto.ts ( 30 bytes)
CREATE src/todos/dto/update-todo.dto.ts ( 169 bytes)
CREATE src/todos/entities/todo.entity.ts ( 21 bytes)
UPDATE package.json ( 2023 bytes)
UPDATE src/app.module.ts ( 312 bytes)
✔ Packages installed successfully.
src/todos/entities/todo.entity.ts export class Todo {
id : string ;
name : string ;
done : boolean ;
}
src/todos/todos.service.ts 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 import { Injectable } from "@nestjs/common" ;
import { CreateTodoDto } from "./dto/create-todo.dto" ;
import { UpdateTodoDto } from "./dto/update-todo.dto" ;
import { Todo } from "./entities/todo.entity" ;
@Injectable ()
export class TodosService {
private todos : Todo [] = [];
constructor () {
this . create ({ name : "Sample #1" , done : false });
this . create ({ name : "Sample #2" , done : false });
this . create ({ name : "Sample #3" , done : true });
}
create ( createTodoDto : CreateTodoDto ) : Todo {
const todo : Todo = {
id : crypto.randomUUID (),
name : createTodoDto.name ,
done : createTodoDto.done ,
};
this . todos = [... this . todos , todo ];
return todo ;
}
findAll () : Todo [] {
return this . todos ;
}
findOne ( id : string ) : Todo {
return this . todos . find (( t ) => t . id === id );
}
update ( id : string , updateTodoDto : UpdateTodoDto ) : Todo {
const todo = this . findOne ( id );
if ( updateTodoDto . name !== undefined ) {
todo . name = updateTodoDto . name ;
}
if ( updateTodoDto . done !== undefined ) {
todo . done = updateTodoDto . done ;
}
return todo ;
}
remove ( id : string ) : void {
this . todos = this . todos . filter (( t ) => t . id !== id );
}
}
src/todos/todos.controller.ts 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 import {
Body ,
Controller ,
Delete ,
Get ,
HttpCode ,
Param ,
Patch ,
Post ,
} from "@nestjs/common" ;
import { CreateTodoDto } from "./dto/create-todo.dto" ;
import { UpdateTodoDto } from "./dto/update-todo.dto" ;
import { TodosService } from "./todos.service" ;
@Controller ( "todos" )
export class TodosController {
constructor ( private readonly todosService : TodosService ) {}
@Post ()
create ( @Body () createTodoDto : CreateTodoDto ) {
return this . todosService . create ( createTodoDto );
}
@Get ()
findAll () {
return this . todosService . findAll ();
}
@Get ( ":id" )
findOne ( @Param ( "id" ) id : string ) {
return this . todosService . findOne ( id );
}
@Patch ( ":id" )
update ( @Param ( "id" ) id : string , @Body () updateTodoDto : UpdateTodoDto ) {
return this . todosService . update ( id , updateTodoDto );
}
@Delete ( ":id" )
@HttpCode ( 204 )
remove ( @Param ( "id" ) id : string ) {
this . todosService . remove ( id );
}
}
src/todos/dto/create-todo.dto.ts import { IsBoolean , IsNotEmpty , IsString } from "class-validator" ;
export class CreateTodoDto {
@IsNotEmpty ()
@IsString ()
name : string ;
@IsNotEmpty ()
@IsBoolean ()
done : boolean ;
}
リクエストのバリデーションを行う
Validation | NestJS - A progressive Node.js framework
$ npm i class-validator class-transformer
DTO にデコレータをつける。
src/todos/dto/create-todo.dto.ts import { IsNotEmpty } from "class-validator" ;
export class CreateTodoDto {
@IsNotEmpty ()
name : string ;
@IsNotEmpty ()
done : boolean ;
}
src/main.ts import { ValidationPipe } from "@nestjs/common" ;
import { NestFactory } from "@nestjs/core" ;
import { AppModule } from "./app.module" ;
async function bootstrap () {
const app = await NestFactory . create ( AppModule );
app . useGlobalPipes ( new ValidationPipe ());
await app . listen ( 3000 );
}
bootstrap ();
エラーハンドリングを行う
Exception filters | NestJS - A progressive Node.js framework
存在しない ID へのリクエストなど、エラーをハンドリングできるようにする。
バックエンド Web アプリケーションではエラーが起きた場合、最終的に HTTP ステータスコードをユーザーに返すことになる。
しかし Service 層は抽象的であるべきで、 HTTP のことを意識したくないので、HTTP ステータスコードについて書くのは最も外界に近い Controller 層が望ましいと思う。
よって Service 層でエラーが起きた場合はErrorを throw することにして、Controller 層は受けたErrorを元に HttpException を throw することにする。
src/todos/errors/todo-not-found-error.ts export class TodoNotFoundError extends Error {}
src/todos/todos.service.ts 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 import { Injectable } from "@nestjs/common" ;
import { CreateTodoDto } from "./dto/create-todo.dto" ;
import { UpdateTodoDto } from "./dto/update-todo.dto" ;
import { Todo } from "./entities/todo.entity" ;
import { TodoNotFoundError } from "./errors/todo-not-found-error" ;
@Injectable ()
export class TodosService {
private todos : Todo [] = [];
constructor () {
console . log ( "contructor" );
this . create ({ name : "Sample #1" , done : false });
this . create ({ name : "Sample #2" , done : false });
this . create ({ name : "Sample #3" , done : true });
}
create ( createTodoDto : CreateTodoDto ) : Todo {
const todo : Todo = {
id : crypto.randomUUID (),
name : createTodoDto.name ,
done : createTodoDto.done ,
};
this . todos = [... this . todos , todo ];
return todo ;
}
findAll () : Todo [] {
return this . todos ;
}
findOne ( id : string ) : Todo {
if ( ! this . exists ( id )) {
throw new TodoNotFoundError ();
}
return this . todos . find (( t ) => t . id === id );
}
update ( id : string , updateTodoDto : UpdateTodoDto ) : Todo {
const todo = this . findOne ( id );
if ( updateTodoDto . name !== undefined ) {
todo . name = updateTodoDto . name ;
}
if ( updateTodoDto . done !== undefined ) {
todo . done = updateTodoDto . done ;
}
return todo ;
}
remove ( id : string ) : void {
if ( ! this . exists ( id )) {
throw new TodoNotFoundError ();
}
this . todos = this . todos . filter (( t ) => t . id !== id );
}
private exists ( id : string ) : boolean {
return this . todos . find (( t ) => t . id === id ) !== undefined ;
}
}
src/todos/todos.controller.ts 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 import {
Body ,
Controller ,
Delete ,
Get ,
HttpCode ,
HttpException ,
Param ,
Patch ,
Post ,
} from "@nestjs/common" ;
import { CreateTodoDto } from "./dto/create-todo.dto" ;
import { UpdateTodoDto } from "./dto/update-todo.dto" ;
import { TodoNotFoundError } from "./errors/todo-not-found-error" ;
import { TodosService } from "./todos.service" ;
@Controller ( "todos" )
export class TodosController {
constructor ( private readonly todosService : TodosService ) {}
@Post ()
create ( @Body () createTodoDto : CreateTodoDto ) {
return this . todosService . create ( createTodoDto );
}
@Get ()
findAll () {
return this . todosService . findAll ();
}
@Get ( ":id" )
findOne ( @Param ( "id" ) id : string ) {
try {
return this . todosService . findOne ( id );
} catch ( error : any ) {
this . catchError ( error );
}
}
@Patch ( ":id" )
update ( @Param ( "id" ) id : string , @Body () updateTodoDto : UpdateTodoDto ) {
try {
return this . todosService . update ( id , updateTodoDto );
} catch ( error : any ) {
this . catchError ( error );
}
}
@Delete ( ":id" )
@HttpCode ( 204 )
remove ( @Param ( "id" ) id : string ) {
try {
this . todosService . remove ( id );
} catch ( error : any ) {
this . catchError ( error );
}
}
private catchError ( error : any ) {
if ( error instanceof TodoNotFoundError ) {
throw new HttpException ( "Not found" , 404 );
} else {
throw new HttpException ( "Internal server error" , 500 );
}
}
}
TypeORM で MySQL
Database | NestJS - A progressive Node.js framework
インターセプター
Interceptors | NestJS - A progressive Node.js framework
CORS
app.enableCors()を使う。
import { ValidationPipe } from "@nestjs/common" ;
import { NestFactory } from "@nestjs/core" ;
import { AppModule } from "./app.module" ;
async function bootstrap () {
const app = await NestFactory . create ( AppModule );
app . useGlobalPipes ( new ValidationPipe ());
app . enableCors ();
await app . listen ( 3000 );
}
bootstrap ();
他のリソースの Service を inject する
TodosService で UsersService を参照する例を示す。
src/users/users.module.ts 1
2
3
4
5
6
7
8
9
10
11
12
13 import { Module } from "@nestjs/common" ;
import { TypeOrmModule } from "@nestjs/typeorm" ;
import { User } from "./entities/user.entity" ;
import { UsersController } from "./users.controller" ;
import { UsersService } from "./users.service" ;
@Module ({
imports : [ TypeOrmModule . forFeature ([ User ])],
controllers : [ UsersController ],
providers : [ UsersService ],
exports : [ UsersService ], // ★
})
export class UsersModule {}
src/todos/todos.module.ts 1
2
3
4
5
6
7
8
9
10
11
12
13 import { Module } from "@nestjs/common" ;
import { TypeOrmModule } from "@nestjs/typeorm" ;
import { UsersModule } from "src/users/users.module" ;
import { Todo } from "./entities/todo.entity" ;
import { TodosController } from "./todos.controller" ;
import { TodosService } from "./todos.service" ;
@Module ({
imports : [ TypeOrmModule . forFeature ([ Todo ]), UsersModule ], // ★
controllers : [ TodosController ],
providers : [ TodosService ],
})
export class TodosModule {}
src/todos/todos.service.ts 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import { Injectable } from "@nestjs/common" ;
import { InjectRepository } from "@nestjs/typeorm" ;
import { UsersService } from "src/users/users.service" ;
import { Repository } from "typeorm" ;
import { CreateTodoDto } from "./dto/create-todo.dto" ;
import { UpdateTodoDto } from "./dto/update-todo.dto" ;
import { Todo } from "./entities/todo.entity" ;
import { TodoNotFoundError } from "./errors/todo-not-found-error" ;
@Injectable ()
export class TodosService {
constructor (
@InjectRepository ( Todo )
private readonly todosRepository : Repository < Todo > ,
private readonly usersService : UsersService
) {}
// 略
}
アップグレード
Angular でいうng updateみたいなものは無い(あったが削除されたらしい)。
以下でアップグレードはできるがベストかは不明。