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

# 前提
環境構築だったりはここ([NestJS でプロジェクト作成から API ドキュメントの表示まで](https://qiita.com/tktcorporation/items/ba2306ae46d99cf37035))でやっていて、この記事はその続き。

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

# やること

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

# やってみる

## Controller の作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252342/b6b4005c-ac2b-0e9c-3d7f-6c00a2d10cdd.png)
ref: https://docs.nestjs.com/controllers

### コマンドで自動生成

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

```bash
$ nest g controller users
```

## Service の作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252342/c9482069-4fe3-aecb-d628-6ac52992ffbf.png)
ref: https://docs.nestjs.com/providers

### コマンドで自動生成

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

```bash
$ nest g service users
```

### users.service.ts
生成されたファイルに軽く処理を追加してみる。

```ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UsersService {
  createFullName = (params: { firstName?: string; lastName?: string }) =>
    `${params.firstName}${params.lastName}`;
}
```

#### テストもだいじ。

<details><summary>users.service.test.ts</summary><div>

```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');
  });
});
```

</div></details>

## Controller から Service を呼び出す

### users.controller.ts

```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/

#### テスト

<details><summary>users.controller.test.ts</summary><div>

```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');
  });
});

```

</div></details>


### レスポンスの確認

#### テスト

<details><summary>users.controller.test.ts</summary><div>

```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' }));
  });
});
```

</div></details>

`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` に閉じ込める。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252342/93955147-71a5-f0f3-da40-e7d0d2ff4d2b.png)

ref: https://docs.nestjs.com/modules

### コマンドで自動生成

```bash
$ nest g module users
```

### 生成されたファイルを編集

```ts
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 が利用できるようになる。

ここ([レスポンスの確認](#%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%81%AE%E7%A2%BA%E8%AA%8D)) で API が使えているのは、コマンドで `UsersController` と `UsersService` を自動生成した際に NestJS が自動で登録を行ってくれているから。このままでも動くが、Module にまとめてあげた方が、依存関係をすっきりと表現できる。

```ts
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 {}
```