🛰️

nest.js + prisma + fastify + postgresqlでREST APIのCRUDを作成する

2021/03/04に公開

概要

node.jsのバックエンドフレームワークnest.jsを使ってREST APIのCRUDを作成します!
ORMにはprisma、DBはpostgresqlを使って作成します
この記事はnest.js 公式を参考に作成しているので、
詳しく調べたい場合は公式ページの方をみて頂けると良いと思います!

nest.jsのセットアップ

nest.jsのインストールにはnest.js用のCLIがあるのでそれを使用しましょう
npm、yarnのどちらかで使用できます

$ yarn global add @nestjs/cli
# プロジェクトを作る
$ nest new nestjs-prisma-crud

# カレントディレクトリに作る場合
$ nest new .

yarn を選択してインストール

? Which package manager would you ❤️  to use? yarn

とりあえずサーバー立ててみる

$ yarn start:dev

http://localhost:3000/ にアクセスして「Hello World!」って出てればセットアップ完了です!

ディレクトリ説明

作成されたプロジェクトを見てみましょう!
nest.jsではプロジェクト作成時にhello worldを返すapiサンプルが用意されています!

仕組みをざっくり説明すると
controllerでリクエストを受け取り、serviceで用意したデータ(hello world)を返します、controllerとserviceの紐付けはmoduleで行っているといった感じ

nestjs-prisma-crud
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
...省略
ファイル名 説明
app.controller.spec.ts テストファイル
app.controller.ts APIのリクエスト受け取り
app.module.ts 依存関係を定義
app.service.ts 処理メソッド
main.ts プロジェクト設定
test E2Eテストコード

必要なライブラリインストール

別途でインストールが必要なライブラリをインストールしましょう
prismaとfastifyのライブラリを入れます
prismaを使う場合pg(postgresql)はインストールする必要がないみたい(typeormでは必要だったので)
@nestjs/mapped-typesはこの先に出てくるdtoというファイルにデフォルトで使われる為入れてます!最終的には不要になります
@nestjs/configは.envを使うためのライブラリです、今回は使わないので入れなくても良いです!

$ yarn add prisma --dev
$ yarn add @prisma/client @nestjs/platform-fastify @nestjs/config @nestjs/mapped-types

ライブラリの設定を行う

prismaとDBのセットアップ

先ほど導入したprismaをセットアップしていきます
initをするとルートディレクトリにprisma.envが作成されます
今回はlocaldbのpostgresqlを使って設定を行っていきます

$ yarn prisma init

nestjs-prisma-crud/prisma/schema.prisma

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

USER、PASSWORD、HOST、PORT、DATABASEを自分の設定に書き換えてください

nestjs-prisma-crud/.env

DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE"

fastifyのセットアップ

fastifyを使用する為にmain.tsを修正します

nestjs-prisma-crud/src/main.ts

import { NestFactory } from '@nestjs/core';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter(),
  );
  await app.listen(3000);
}
bootstrap();

環境変数(env)のセットアップ

envの使用準備はConfigModuleを以下のように定義すれば
.envファイルを使用する事ができるようになります

import { ConfigModule } from '@nestjs/config';

...

process.env.HOGE

prisma migrateでDBテーブルを作成

apiを作成する前にprismaのマイグレート機能を使ってDBテーブルを作成します
先ほど作成したschema.prismaにモデル定義を行います
schema.prismを使ってマイグレーションも行えるので、
今回はtaskというテーブルを作ってみます

nestjs-prisma-crud/prisma/schema.prisma

model task {
  id      Int     @default(autoincrement()) @id
  title   String
  content String?
}

マイグレーションは以下のコマンドで実行することが可能です
マイグレーションの名前を聞かれるのでinitとでもつけておきましょう(初回なので)

$ yarn prisma migrate dev --preview-feature
✔ Name of migration … init
The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20210301135608_init/
    └─ migration.sql

prismaフォルダにDB作成に使われたsqlなどが自動生成されます

nestjs-prisma-crud/prisma
├── migrations
│   ├── 20210301135608_init
│   │   └── migration.sql
│   └── migration_lock.toml
└── schema.prisma

DBクライアントから確認するとtaskというテーブルが作成されている事が確認できました

最後に下記のコメントを実行することで、
@prisma/clientからtaskテーブルを取り扱うことができるようになります

$ yarn prisma generate

REST APIでCRUDを作成する

apiの作成準備が整ったのでCRUD APIを作ってみましょう

nest.jsのCRUDジェネレーターを使ってみる

nest.jsには「CRUDジェネレーター」なるものがあるのでこれでAPIの雛形を作っていきます!
nest g resourceで実行し質問形式で雛形が作れるので、
先ほど作成したテーブルに合わせてtaskというモデルでREST APIで作成してみます!

$ nest g resource
? What name would you like to use for this resource (plural, e.g., "users")? task
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes

作成されるファイル

以下のようなファイルが生成されます
controllermoduleserviceの他にもいくつかファイルができてますね
簡単に説明すると
dtoはパラメータの型定義やバリデーションに使用できます
ちなみにdtoは「Data Transfer Object」の略らしい
entitiesはオブジェクト定義に使用、TypeORMの場合にマイグレーションにも使えます

nestjs-prisma-crud/src/tasks
├── dto
│   ├── create-task.dto.ts
│   └── update-task.dto.ts
├── entities
│   └── task.entity.ts
├── task.controller.spec.ts
├── task.controller.ts
├── task.module.ts
├── task.service.spec.ts
└── task.service.ts
ファイル名 説明
create-task.dto.ts postで使用されるパラメータの型定義
update-task.dto.ts putで使われるパラメータの型定義
task.entity.ts オブジェクト定義
task.controller.ts APIのリクエスト受け取り
task.module.ts 依存関係を定義
task.service.spec.ts テストファイル
task.service.ts 処理メソッド

controllerでリクエスト受け取れるかチェック

controllerをみるとわかるように以下のようなルーティングができているので、
サーバー立ててチェックしてみます

  • /task, POST
  • /task, GET
  • /task/:id, GET
  • /task/:id, PUT
  • /task/:id, DELETE
$ yarn start:dev

# {/task, GET}にリクエスト
$ curl localhost:3000/task
>> This action returns all

CRUDを作る

リクエストが受け取れるのを確認できたので
APIでDB操作ができるようにしていきます!

まず初めにprisma/clientを使う準備をします
srcディレクトリ内にprisma.service.tsを作成して下記のコードを追加します
これだけで使用準備OK!

nestjs-prisma-crud/src/prisma.service.ts

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient
  implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

次にに不要なファイルを消しちゃいましょう
今回はprismaが用意してくれるパラメータの型定義を使用するので、
dto、entitiesは使わないのでファイルごと消しちゃいました!

moduleに依存関係を定義します
providersにPrismaServiceを追加するだけですね!

nestjs-prisma-crud/src/task/task.module.ts

import { Module } from '@nestjs/common';
import { TaskService } from './task.service';
import { TaskController } from './task.controller';
import { PrismaService } from './../prisma.service';

@Module({
  controllers: [TaskController],
  providers: [TaskService, PrismaService],
})
export class TaskModule {}

controllerを修正します!
serviceからレスポンスを受け取れるように型をつけます
@prisma/clientからtaskの型定義を使っていますが、
これは先ほどtaskモデルをschema.prisma登録してyarn prisma generateをしてから使えるようになっています
どんな事ができるかはprisma公式をみてもらえると!

nestjs-prisma-crud/src/task/task.controller.ts

import {
  Controller,
  Get,
  Post,
  Body,
  Put,
  Param,
  Delete,
} from '@nestjs/common';
import { TaskService } from './task.service';
import { task, Prisma } from '@prisma/client';

@Controller('task')
export class TaskController {
  constructor(private readonly taskService: TaskService) {}

  @Post()
  async create(@Body() data: Prisma.taskCreateInput): Promise<task> {
    return this.taskService.create(data);
  }

  @Get()
  async findAll(): Promise<task[]> {
    return this.taskService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: string): Promise<task> {
    return this.taskService.findOne(+id);
  }

  @Put(':id')
  async update(
    @Param('id') id: string,
    @Body() data: Prisma.taskUpdateInput,
  ): Promise<task> {
    return this.taskService.update(+id, data);
  }

  @Delete(':id')
  async remove(@Param('id') id: string): Promise<task> {
    return this.taskService.remove(+id);
  }
}

最後に実際に処理を行うserviceの方を実装していきます!
実装にはこちらのリポジトリが参考になりました!

nestjs-prisma-crud/src/task/task.service.ts

import { Injectable } from '@nestjs/common';
import { PrismaService } from './../prisma.service';
import { task, Prisma } from '@prisma/client';

@Injectable()
export class TaskService {
  constructor(private prisma: PrismaService) {}

  async create(data: Prisma.taskCreateInput): Promise<task> {
    return this.prisma.task.create({ data });
  }

  async findAll(): Promise<task[]> {
    return this.prisma.task.findMany();
  }

  async findOne(id: number) {
    return this.prisma.task.findUnique({
      where: { id: id },
    });
  }

  async update(id: number, data: Prisma.taskUpdateInput): Promise<task> {
    return this.prisma.task.update({
      where: { id: id },
      data,
    });
  }

  async remove(id: number): Promise<task> {
    return this.prisma.task.delete({
      where: { id: id },
    });
  }
}

これで実装は以上です!
次は早速作ったAPIを動かしてみます!

APIを動かしてみる

curlでAPIにリクエストを投げてレスポンスを確認してみます!

$ yarn start:dev
  • task Post()
$ curl -X POST localhost:3000/task -H 'Content-Type:application/json' -d "{\"title\":\"あいうえお\", \"content\": \"アイウエオ\"}"

  • task Get()
$ curl -X GET localhost:3000/task
[
  {"id":1,"title":"あいうえお","content":"アイウエオ"},
  {"id":2,"title":"かきくけこ","content":"カキクケコ"},
  {"id":3,"title":"さしすせそ","content":"サシスセソ"}
]
  • task Get(':id')
$ curl -X GET localhost:3000/task/1
{"id":1,"title":"あいうえお","content":"アイウエオ"}
  • task Put(':id')
$ curl -X PUT localhost:3000/task/1 -H 'Content-Type:application/json' -d "{\"title\":\"あああああ\", \"content\": \"いいいいい\"}"
{"id":1,"title":"あああああ","content":"いいいいい"}
  • task Delete(':id')
$ curl -X DELETE localhost:3000/task/1
{"id":1,"title":"あああああ","content":"いいいいい"}

gitリポジトリ

今回はnest.jsの一部しか使っていませんが、
割と簡単にAPIを作る事ができました!
prismaも導入が楽で、使用方法もシンプルでよかったです!
prismaはgraphQLとの相性も良いらしく、nest.jsでgraphQLの雛形も作れるので、
graphQLで作ってみても良いですね!

今回使ったリポジトリはこちらになります!
https://github.com/git-gen/nestjs-prisma-crud

Discussion