Fusic Tech Blog

Fusion of Society, IT and Culture

NestJS + TypeORMでリポジトリパターンを実装する
2019/08/06

NestJS + TypeORMでリポジトリパターンを実装する

最近はフロントエンドアプリケーションをTypeScriptで開発することが多くなってきました。
そこでバックエンドもTypeScriptで開発することによって、言語統一による開発の効率化や、Interfaceの共有などができるのではないかと考えています。
その中でもAngularライクなTypeScriptのバックエンドフレームワークであるNestJSに注目しています。
今回は手始めにTypeScriptのORマッパーであるTypeORMを使ってのデータベース連携をしたNestJSアプリケーションの開発手順を説明します。

開発環境セットアップ

まず、Dockerを使って簡単に開発環境を構築します。

Dockerfileは以下のようになりました。

FROM node:12-alpine

RUN npm install -g @nestjs/cli@6.5
RUN apk update && apk add git

RUN mkdir workspace
WORKDIR /workspace

そして、docker-compose.ymlは以下のようになります。

version: "3"
services:
  nest-app-web:
    build: .
    ports:
      - 3040:3000
    volumes:
      - ./:/workspace/
    links:
      - nest-app-db
    tty: true
  nest-app-db:
    image: mysql:5.7
    volumes:
      - mysql-data
    environment:
      MYSQL_ROOT_PASSWORD: "root"
      MYSQL_USER: "root"
      MYSQL_PASSWORD: "root"
      MYSQL_DATABASE: "root"
    ports:
      - 13306:3306
volumes:
  mysql-data:

その後コンテナを立ち上げます。

$ docker-compose up -d

プロジェクト作成

コンテナが起動したら、以下コマンドでプロジェクト作成、必要なパッケージのインストールを行います。 すると、 npm run start コマンドで開発サーバーが起動できると思います。 開発の効率化のためにHMRを有効にするには前回の記事 が参考になります。

$ docker-compose exec nest-app-web nest new .
$ docker-compose exec npm install
$ docker-compose exec npm run start

上手くいけば、localhost:3040へアクセスでき、"Hello World!" と表示されるはずです。

TypeORM導入

$ docker-compose exec nest-app-web npm install @nestjs/typeorm typeorm mysql

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { join } from 'path'; // <- 追加

@Module({
  imports: [TypeOrmModule.forRoot({
    type: 'mysql',
    host: 'MY_HOST',
    port: 3306,
    username: 'MY_USERNAME',
    password: 'MY_PASSWORD',
    database: 'MY_DATABASE',
    entities: [join(__dirname, '**/**.entity{.ts,.js}')],
    synchronize: true,
    keepConnectionAlive: true,
  })], // <- 追加
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

ormconfig.jsonを作成して接続情報を記述すれば forRoot() 内は省略できるそうですが、syntax errorが出たので、直接記述しています。

リポジトリパターンの実装

モジュールの作成

Angularを知っている人はわかると思いますが、NestJSはモジュールという単位でアプリケーションが区切られてルートモジュール(app.module.ts)を元に紐づいていきます。

参考: https://docs.nestjs.com/modules

$ docker-compose exec nest-app-web nest g mo Posts

posts/posts.module.ts

import { Module } from '@nestjs/common';

@Module({})
export class PostsModule {}

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostsModule } from './posts/posts.module';

@Module({
  imports: [TypeOrmModule.forRoot(), PostsModule], // <- 追加される
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

エンティティクラスの作成

エンティティクラスはデータベース直接紐づくクラスで、ここで定義したものがそのままデータベースのテーブルになります。

TypeORMのsynchronize をtrueにすると、実行時に自動でテーブルが作成されます。

エンティティは残念ながら生成コマンドは無いようなので、自分でファイルを作成します。

posts/post.entity.ts

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 500 })
  title: string;

  @Column('text')
  body: string;
}

posts.module.tsにエンティティを設定します。

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Post } from './post.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Post])], // <- 追加する
})
export class PostsModule {}

サービスクラスの作成

ビジネスロジックを担うサービスクラスですが、NestJSではProviderという概念で扱われ依存性注入(Dependency Injection)によってクラス間を疎結合な関係を保つことができます。

$ docker-compose exec nest-app-web nest g s Posts

posts/posts.module.ts

import { Module } from '@nestjs/common';
import { PostsService } from './posts.service';

@Module({
  providers: [PostsService], // <- 追加される
})
export class PostsModule {}

posts/posts.service.ts

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

@Injectable()
export class PostsService {}

生成されてサービスクラスを編集、@InjectRepository() デコレータでエンティティクラスからリポジトリクラスを呼び出し、サービスクラスに対して依存注入して呼び出しています。

import { Injectable, Inject } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Post } from './post.entity';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private readonly postRepository: Repository<Post>,
  ) {}

  async findAll(): Promise<Post[]> {
    return await this.postRepository.find();
  }
}

コントローラクラスの作成

次はリクエストとレスポンスをさばくコントローラクラスです。

$ docker-compose exec nest-app-web nest g co Posts

posts/posts.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Post } from './post.entity';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';

@Module({
  imports: [TypeOrmModule.forFeature([Post])],
  providers: [PostsService],
  controllers: [PostsController], // <- 追加される
})
export class PostsModule {}

posts/posts.controller.ts

import { Controller } from '@nestjs/common';

@Controller('posts')
export class PostsController {}

先ほど用意したサービスクラスをコントローラクラスに依存注入して使い、Post一覧を返すメソッドを定義します。

import { Controller, Get } from '@nestjs/common';
import { PostsService } from './posts.service';
import { Post } from './post.entity';

@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}
  @Get()
  findAll(): Promise<Post[]> {
    return this.postsService.findAll();
  }
}

すると、localhost:3040/posts へアクセスするとデータベースにあるレコード一覧のjsonが返ってきます。

最後に

TypeORMを利用することでデータベースと連携したNestJSアプリケーション開発ができるようになりました。
さらにモジュール、エンティティ、サービス、コントローラそれぞれのクラスを作成することで「関心の分離」を実現することができました。

さらにNestJSのCRUDパッケージ(@nestjsx/crud)と組み合わせることで簡単に CRUDを構築 することができるようですので試してみたいと思います。

Daiki Urata

Daiki Urata

フロントエンド好きなエンジニアです。