1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NestJS で簡単な API を実装してみる

Last updated at Posted at 2020-05-27

前提

環境構築だったりはここ(NestJS でプロジェクト作成から API ドキュメントの表示まで)でやっていて、この記事はその続き。

ソースコード

ここで作成したコード全文は Github から確認できる
https://github.com/tktcorporation/nestjs-sample/tree/create-some-api

やること

  • /users のパスで使える API を作成する
  • 作成する API は GETPOST で処理が切り分けられる
  • ビジネスロジックは Service に置く
  • DB からデータを取ってきたりはここでは扱わない

やってみる

Controller の作成

image.png
ref: https://docs.nestjs.com/controllers

コマンドで自動生成

下記のコマンドを実行すると、 src/users/users.controller.ts が作成される。

$ nest g controller users

Service の作成

image.png
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 の中で完結するような構成になっている。
ここでは、UsersControllerUsersService の依存関係を UsersModule に閉じ込める。

image.png

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 が使えているのは、コマンドで UsersControllerUsersService を自動生成した際に 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 {}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?