前提
環境構築だったりはここ(NestJS でプロジェクト作成から API ドキュメントの表示まで)でやっていて、この記事はその続き。
ソースコード
ここで作成したコード全文は Github から確認できる
https://github.com/tktcorporation/nestjs-sample/tree/create-some-api
やること
-
/users
のパスで使える API を作成する - 作成する API は
GET
とPOST
で処理が切り分けられる - ビジネスロジックは Service に置く
- DB からデータを取ってきたりはここでは扱わない
やってみる
Controller の作成
ref: https://docs.nestjs.com/controllers
コマンドで自動生成
下記のコマンドを実行すると、 src/users/users.controller.ts
が作成される。
$ nest g controller users
Service の作成
ref: https://docs.nestjs.com/providers
コマンドで自動生成
下記のコマンドを実行すると、 src/users/users.service.ts
が作成される。
$ nest g service users
users.service.ts
生成されたファイルに軽く処理を追加してみる。
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
createFullName = (params: { firstName?: string; lastName?: string }) =>
`${params.firstName}${params.lastName}`;
}
テストもだいじ。
users.service.test.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('createFullName', () => {
const firstName = 'John';
const lastName = 'Doe';
expect(service.createFullName({ firstName, lastName })).toBe('JohnDoe');
});
});
Controller から Service を呼び出す
users.controller.ts
import { Controller, Get, Query, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
// GET の場合は QueryStrings からパラメータを取得
@Get()
getFullName(
@Query() createFullNameDto: { first_name?: string; last_name?: string },
) {
return {
full_name: this.usersService.createFullName({
firstName: createFullNameDto.first_name,
lastName: createFullNameDto.last_name,
}),
};
}
// POST の場合は Body からパラメータを取得
@Post()
getFullNameAndMethod(
@Body() createFullNameDto: { first_name?: string; last_name?: string },
) {
return {
method: 'post',
full_name: this.usersService.createFullName({
firstName: createFullNameDto.first_name,
lastName: createFullNameDto.last_name,
}),
};
}
}
参考: https://area-b.com/blog/2018/09/12/1930/
テスト
users.controller.test.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersModule } from './users.module';
import { UsersService } from './users.service';
describe('Users Controller', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
controllers: [UsersController],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
it('getFullName', () => {
const first = 'John';
const last = 'Doe';
const result = controller.getFullName({
first_name: first,
last_name: last,
});
expect(result.full_name).toBe(`${first}${last}`);
});
it('getFullName', () => {
const first = 'John';
const last = 'Doe';
const result = controller.getFullNameAndMethod({
first_name: first,
last_name: last,
});
expect(result.full_name).toBe(`${first}${last}`);
expect(result.method).toBe('post');
});
});
レスポンスの確認
テスト
users.controller.test.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
it('/users (GET)', () => {
return request(app.getHttpServer())
.get('/users?first_name=John&last_name=Doe')
.expect(200)
.expect(JSON.stringify({ full_name: 'JohnDoe' }));
});
it('/users (POST)', () => {
return request(app.getHttpServer())
.post('/users')
.send({ first_name: 'John', last_name: 'Doe' })
.expect(201)
.expect(JSON.stringify({ method: 'post', full_name: 'JohnDoe' }));
});
});
yarn start
$ curl -X POST http://{DockerIP}:3000/users -H "Content-Type:application/json" -d '{"first_name":"John","last_name":"Doe"}'
=> {"method":"post","full_name":"JohnDoe"}
$ curl "http://{DockerIP}:3000/users?first_name=John&last_name=Doe"
=> {"full_name":"JohnDoe"}
Module を作成する
NestJS は Modules と呼ばれるかたまりから構成されていて、依存関係が各 Module の中で完結するような構成になっている。
ここでは、UsersController
と UsersService
の依存関係を UsersModule
に閉じ込める。
ref: https://docs.nestjs.com/modules
コマンドで自動生成
$ nest g module users
生成されたファイルを編集
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersModule],
})
export class UsersModule {}
Application に登録
app.module.ts
を編集して AppModule
に登録することで Module
に含まれている API が利用できるようになる。
ここ(レスポンスの確認) で API が使えているのは、コマンドで UsersController
と UsersService
を自動生成した際に NestJS が自動で登録を行ってくれているから。このままでも動くが、Module にまとめてあげた方が、依存関係をすっきりと表現できる。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
@Module({
imports: [UsersModule],
// UsersModule をインポートするので、controllers, providers から Users 関連のものを削除する
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}