💨

軽はずみな気持ちでNestJSに入門する

2023/12/05に公開

こんにちは!株式会社アルダグラムでWebエンジニアをしている大木と申します!

本記事は株式会社アルダグラム Advent Calendar 2023 5日目の記事になります。盛り上がっていきましょう!!!
今回は、NestJS を軽はずみな気持ちで勉強していこうかと思います。

アルダグラムでは「KANNA」というプロジェクト管理・電子帳票入力のサービスプラットフォームを提供しております。プロジェクト管理アプリにおいては、特にエンタープライズ企業のお客様などで、機能を個別にカスタマイズし基幹システムと連携をするなどといった利用方法が存在します。そういった機能への対応として、「KANNA OpenAPI」という機能を介してスムーズに連携できるように設計がされています。

この KANNA OpenAPI でNestJSが利用されているのですが、筆者が所属しているチームとこの機能を開発しているチームが異なるといった背景もあり正直知見がありません\(^o^)/
最近はサーバーサイドでTypeScriptが採用されるケースでNestJSを利用されたり、BFFの文脈で耳にすることも増えたような気がします。そんなこんなでNestJSの基本を学んでいきたいと思います。

サクッとエンドポイントを作ってみる

まずはインストールからです。

https://docs.nestjs.com/

公式に手順が書いてありますね。この2つでプロジェクトが立ち上がってしまうようです。もう完全に理解してしまったかもしれない。

$ npm i -g @nestjs/cli

$ nest new project-name

公式のドキュメントでは Cats をサンプルに取り上げていましたが、今回は Todos という命名であれこれ学んでいきたいと思います。

Controllerを作ってみる

プロジェクトを立ち上げた直後は AppController というControllerのみが存在しています。 TodosController を作成していこうかと思います。

todos.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('todos')
export class TodosController {
  @Get()
  findAll(): string {
    return '全てのTODOを返すよ!';
  }
}

作成したControllerを app.module.ts に追加してみます。

app.controller.ts
controllers: [AppController, TodosController],

/todos のエンドポイントを叩いてみると… 意図した感じになっていました。

NestJSはデコレータを使ってあれこれしていくんだなというのがこれだけでもわかるような気がします。筆者は2年ほど前まで Angular を使って開発していたので、懐かしい気持ちになりました。NestJS自体、 Angular から影響を受けているみたいですね。

Serviceを作ってみる

実際のアプリケーションではDBとの接続や外部APIとの連携など、より複雑な実装をすることになります。

今回はTodoのデータ管理に責務を持つ TodosService を作っていきたいと思います。

todo.ts
export type Todo = {
  title: string;
  description: string;
}

Serviceでは Injectable というデコレータを利用します。

todos.service.ts
import { Injectable } from '@nestjs/common';
import { Todo } from './interface/todo';

@Injectable()
export class TodosService {
  private readonly todos: Todo[] = [];

  create(todo: Todo) {
    this.todos.push(todo);
  }

  findAll(): Todo[] {
    return this.todos;
  }
}

ControllerではconstructorでそのServiceを受け取ることで、Serviceの実装の内部を隠蔽させることができます。NestJSではDIの仕組みも標準で含まれているんですね。

todos.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { TodosService } from './todos.service';
import { CreateTodoDto } from './create-todo-dto';

@Controller('todos')
export class TodosController {
  constructor(private todosService: TodosService) {}

  @Get()
  async findAll() {
    return this.todosService.findAll();
  }

  @Post()
  async create(@Body() createTodoDto: CreateTodoDto) {
    return this.todosService.create(createTodoDto);
  }
}

チョコっとだけ変えてみる

個人的には、クラスを直接注入するのではなくInterfaceに依存させるような形が望ましいのではと考えたため以下のようにしてみました。

ServiceのInterfaceを作成し、元々作っていた TodosService はそれを実装するような形にします。

todos-service-interface.ts
import { Todo } from './interface/todo';

export interface TodosServiceInterface {
  create(todo: Todo);
  findAll(): Todo[];
}
todos.service.ts
export class TodosService implements TodosServiceInterface {

実際に利用するControllerは、そのInterfaceに依存するような形にさせるといった感じです。これで元々は TodoService という具象に依存していた部分を解消させることができました。

todos.controller.ts
export class TodosController {
  constructor(
    @Inject('TodosServiceInterface')
    private todosService: TodosServiceInterface,
  ) {}
  ...
}

Moduleを作ってみる

アプリケーションの境界を適切に区切るためにModuleの機能も提供されているようです。Todosに関連する Controller や Service などといったリソースを全てルートの Module から参照させるととんでもないことになりそうですね。Module として切り出し、うまく分割させていきたいものです。

先程まで実装していたものは全て TodosModule に移動させました。

todos.module.ts
import { Module } from '@nestjs/common';
import { TodosController } from './todos.controller';
import { TodosService } from './todos.service';

@Module({
  controllers: [TodosController],
  providers: [
    {
      provide: 'TodosServiceInterface',
      useClass: TodosService,
    },
  ],
})
export class TodosModule {}

ルートのModuleでは、 TodosModule をインポートするだけでうまく分割ができました。

app.module.ts
@Module({
  imports: [TodosModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

https://docs.nestjs.com/modules

のページにあるように、ドキュメントの各所でアプリケーションの構成をイメージさせるような図がありとてもわかりやすいなぁと思いました。

サクッと学んでみた感想

今回はNestJSの基本中の基本となる概念や使い方を学んでみました。今回触れた部分だけでも、アプリケーションをより高品質に保つための原則をもとにそれを実現させるスキームがかなり備わっているように見受けられました。Typescriptの恩恵も相まって、フロントエンド・バックエンドで一貫した言語を利用するというのも確かに選択肢としてはありそうだなぁと感じさせられました。

軽はずみな気持ちで入門してみましたが、まだまだ深掘りできる部分がありそうです。ORMなどを利用したもう少し実践的な利用方法も学んでいきたいです。

アルダグラム Tech Blog

Discussion