はじめに
Node.js/Express/routing-controllers+TypeScript+TypeORM+PostgreSQLでWeb API作成で、自分なりに納得いく構成で Web API を作成したが、NestJS なるバックエンドフレームワークの存在を知ってしまったので、そちらも試したところ素晴らしかったので採用。今後 TypeScript での Web API 作成は NestJS+TypeORM+PostgreSQL を使っていきたい。実行環境は以下。
- Node.js: 14.17.6
- npm: 6.14.15
- NestJS: 8.0.6
- typeorm: 0.2.37
NestJS のインストールおよびプロジェクト作成
First steps | NestJS にしたがって、NestJS をインストールし、プロジェクトを作成する。
$ npm install -g @nestjs/cli
$ nest new api
以下コマンドで動作確認を行う。
$ cd api
$ npm run start
# 別ターミナルにて
$ curl http://localhost:3000
Hello World!
typeorm のインストール
TypeORM にしたがって、以下パッケージをインストールする。PostgreSQL 以外の DB と連携する場合は pg ではなく、適切なパッケージをインストールする。
$ npm install typeorm pg
$ npm install -g typeorm
$ npm install @nestjs/typeorm
ディレクトリ構成
NestJS プロジェクトのディレクトリ構成(一部抜粋)は controller, entity, service ごとの単位ではなく、モジュール単位で構成している。NestJS は Angular に強くインスパイアされており、以下の photo のようにモジュール単位での構成を前提にしているためである。実際、後ほど実行する nest generate
コマンドはモジュール単位となるようにファイルが作成される。(たとえば nest generate module photo
を実行すると、src/photo/photo.module.ts のように photo ディレクトリも合わせて作成される。)
.
├── ormconfig.json # TypeORM 設定ファイル、package.json と同階層に置く
├── package-lock.json
├── package.json
├── src
│ ├── app.module.ts # 各モジュールを統合する AppModule
│ ├── main.ts # アプリケーションのエントリファイル
│ └── photo
│ ├── photo.controller.ts # PhotoController
│ ├── photo.entity.ts # Photo
│ ├── photo.module.ts # PhotoModule
│ └── photo.service.ts # PhotoService
└── tsconfig.json
以下、簡単に用語を説明する。
- Entity : モデルを修飾したクラス。このモデルに基づいた DB テーブルを作成でき、データの追加・削除などの操作もできる。
- Controller : ルートとリクエストに対しての動作を定義したクラス。コントローラに注入されたサービスが実際の処理を行う。
- Service : 具体的な処理を定義したクラス。レポジトリを使って、エンティティの管理などを行う。
- Module : 上記のようなクラスをまとめて1つのモジュールとして統合するクラス。結合が疎となるアーキテクチャを実現する。
Photo モジュールの作成
以下で NestJS + TypeORM + PostgreSQL を用いて Photo API を作成していく。(Photo API は TypeORM で例としているものと同じものである。)
大まかな流れは以下の通り。
- Photo エンティティの作成
- PhotoService の作成
- PhotoController の作成
- PhotoModule の作成
- AppModule への追加
- ormconfig.json の作成
- 動作確認
1. Photo エンティティの作成
以下コマンドで、photo.entity.ts ファイルを作成する。(エンティティは nest ではなく typeorm に関わる部分であるため、nest generate
コマンドが存在しない。先述のディレクトリ構成で述べたように nest generate
コマンドをあらかじめ実行しておくと photo ディレクトリも作成されるが、説明する順番の都合上ここでは mkdir
コマンドでディレクトリを作成している。)
$ mkdir src/photo
$ touch src/photo/photo.entity.ts
エンティティクラスは以下のように定義した。モデルのプロパティに対して @Column
でカラムを指定し、@Entity
でモデルをエンティティ化している。(詳細な説明は割愛。)
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id!: number;
@Column({
length: 100
})
name!: string;
@Column('text')
description!: string;
@Column()
filename!: string;
@Column()
views!: number;
@Column()
isPublished!: boolean;
}
2. サービスの作成
以下コマンドで photo.service.ts を作成する。--no-spec
は photo.service.spec.ts を作成しないためのオプションである。
$ nest generate service photo --no-spec
サービスクラスは以下のように定義した。@Injectable
により他サービスやコントローラに注入可能にしており、@InjectRepository
により上記で定義した Photo エンティティのレポジトリを操作可能になる。また具体的な処理として、「写真の追加」と「写真リストの取得」という2つのメソッドを定義している。
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';
@Injectable()
export class PhotoService {
constructor(
@InjectRepository(Photo)
private readonly photoRepository: Repository<Photo>
) { }
async addPhoto(photo: Photo) {
this.photoRepository.save(photo);
}
async getPhotoList() {
return await this.photoRepository.find();
}
}
3. コントローラの作成
以下コマンドで photo.controller.ts を作成する。
$ nest generate controller photo --no-spec
コントローラクラスは以下のように定義した。@Controller
の引数にてルートを指定しており、これにより http://localhost:3000/photo
から始まるリクエストに関する処理を以下メソッドで実行する。また上記で作成した PhotoService を注入し、@Post/@Get
で修飾することで各メソッドを Post/Get リクエストに対する処理に対応付けている。
import { Body, Controller, Get, Post } from '@nestjs/common';
import { Photo } from './photo.entity';
import { PhotoService } from './photo.service';
@Controller('photo')
export class PhotoController {
constructor(private readonly service: PhotoService) { }
@Post()
createPhoto(@Body() photo: Photo) {
this.service.addPhoto(photo);
}
@Get()
getPhotoList() {
return this.service.getPhotoList();
}
}
4. モジュールの作成
以下コマンドで photo.module.ts を作成する。
$ nest generate module photo
モジュールクラスは以下のように定義した。@Module
にて、使用するコントローラを controllers
に、使用するサービスを providers
に指定している。また imports
にて、TypeORM と連携して Photo エンティティを扱えるようにしている。
import { Module } from '@nestjs/common';
import { Photo } from './photo.entity';
import { PhotoController } from './photo.controller';
import { PhotoService } from './photo.service';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([Photo])],
controllers: [PhotoController],
providers: [PhotoService]
})
export class PhotoModule {}
5. App モジュールへ PhotoModule の追加
TypeORM と連携するための TypeOrmModule と、上記で定義した PhotoModule を読み込む。TypeOrmModule.forRoot() の引数にて、接続する DB の情報を設定することもできるが、コードが見づらくなるため後述の ormconfig.json にて設定する。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PhotoModule } from './photo/photo.module';
@Module({
imports: [
TypeOrmModule.forRoot(),
PhotoModule
]
})
export class AppModule {}
6. ormconfig.json の作成
接続する DB の情報などを設定する ormconfig.json を作成する。コードは以下。
{
"type": "postgres",
"host": "172.21.0.1",
"port": 5432,
"username": "postgres",
"password": "postgres",
"database": "test_db",
"entities": [
"dist/**/*.entity.js"
],
"synchronize": true
}
Node.js/Express/routing-controllers+TypeScript+TypeORM+PostgreSQLでWeb API作成と同様にしたところ、SyntaxError: Cannot use import statement outside a module
というエラーが発生した。こちらを参考に、"entities": ["dist/**/*.entity.js"]
としたところ解消した。上記リンクでも述べているため、詳細は割愛。
7. 動作確認
以下コマンドでアプリケーションを起動する。
$ npm run start
あとは curl
コマンドや Postman などで、http://localhost:3000/photo
に対して Post リクエストや Get リクエストを送れば動作が確認できる。
おわりに
NestJS + TypeORM + PostgreSQL にて、Web API を作成することができた。ひとまずこの組み合わせで納得したので、認証などより複雑な処理を行えるように引き続き勉強していきたい。