🍇

NestJS + graphQL + Prisma + Firebase でToDOリストを作ろう!〜セットアップ編〜

2024/07/08に公開

はじめに

NestJS + Prisma + graphql + Firebaseを利用してToDoリスト用のAPIサーバーを構築します!

今回はNestJSにORMとgraphqlを組み込むところまでやっていきます。

この記事で制作したコードはコチラ
https://github.com/Shige031/nestjs-graphql-prisma-starter

前提

nodeのバージョン: 20.11.1

Nest.jsプロジェクト作成

まず、Nest.jsのプロジェクトを作成します。

Nest.js Cliをインストール

ターミナル
npm i -g @nestjs/cli

Nest.jsプロジェクト作成

ターミナル
nest new nestjs-graphql-prisma-starter

この状態でnest.jsを起動したら、localhost:3000にアクセスし、「Hello world」と表示されたらOK!

ターミナル
npm run start

その他

以下のファイルは今回使わないので削除しておきましょう。

  • app.controller.spec.ts
  • app.controller.ts
  • app.service.ts
  • test/app.e2e-spec.ts
  • test/jest-e2e.json

ローカル用データベース環境構築

ローカルで開発を行うためのデータベースを用意します。

docker-compose.ymlファイルを配置

以下のファイルをルート下に配置してください。

docker-compose.yml
version: '3.8'

networks:
  starter:

services:
  starter-db:
    image: mysql:8.0
    env_file:
      - ./docker/mysql.env
    container_name: 'starter-db'
    restart: 'no'
    ports:
      - 5306:3306
    volumes:
      - ./docker/conf/mysql.cnf:/etc/mysql/conf.d/mysql.conf.cnf
      - ./docker/data:/var/lib/mysql
      - ./docker/logs:/var/log/mysql
    networks:
      - starter
    cap_add:
      - SYS_NICE # CAP_SYS_NICE

今回は詳しい解説は省きますが、MySQL8.0のコンテナをホスト側ポートを5306、コンテナ側ポートを3306と指定して立てています。

ローカルDB用のenvファイルを配置

ルート下にdockerというフォルダを切り、中にmysql.envファイルを配置します。

docker/mysql.env
MYSQL_ROOT_PASSWORD=password
MYSQL_PORT=5306
MYSQL_USER=starter-api
MYSQL_PASSWORD=password
MYSQL_DATABASE=starter-db
MYSQL_LOWER_CASE_TABLE_NAMES=0

dockerコンテナ立ち上げ

ここまでできたらコンテナを起動できます。

ターミナル
docker-compose up -d

正常に起動したかどうか確認してみましょう

ターミナル
docker ps -a

statusがUpとなっていればOKです!

Prisma導入

今回はORMとしてPrismaを使用します。以下のページを参考にしています。
Nest.js * Prisma公式ドキュメント
https://docs.nestjs.com/recipes/prisma
Prisma公式ドキュメント
https://www.prisma.io/docs

Prismaセットアップ

まずはPrisma CLIを開発用パッケージとしてインストール。

ターミナル
npm install prisma --save-dev

ローカルでPrisma CLIを起動。

ターミナル
npx prisma

Prismaの初期化。

ターミナル
npx prisma init

するとルート下にprismaフォルダが作成され、中にschema.prismaファイルが入っているはずです。

schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

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

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

このファイルを以下のように修正します。

schema.prisma
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

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

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

また、.envファイルができているはずなので、その中にデータベースのURLを記述します。

.env
DATABASE_URL="mysql://root:password@localhost:5306/starter-db"

これでPrisma〜DB間の設定は完了です。

テーブル作成

早速テーブルを作ってみましょう。

schema.prisma
model User {
  id          String   @id @default(cuid())
  firebaseUId String   @unique
  name        String
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  todos Todo[]
}

enum TodoStatus {
  // 未完了
  NOT_STARTED
  // 進行中
  IN_PROGRESS
  // 完了
  COMPLETED
}

model Todo {
  id          String     @id @default(cuid())
  userId      String
  title       String
  description String
  status      TodoStatus @default(NOT_STARTED)
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

User-Todoが1-多のリレーションを持っています。
また、onDelete: Cascadeを設定することにより、紐づくUserが削除された場合、Todoも自動的に削除されるようになります。
その他書き方については公式ドキュメントをご覧ください。

マイグレート

Prismaモデルを配置すると、そこからSQL移行ファイルを生成し、データベースに対して実行できます。この一連の処理をマイグレートと呼びます。
早速実行してみましょう。
※実行するとマイグレーションにつける名前を聞かれるので、適当に入力してください。

ターミナル
npx prisma migrate dev

成功するとprismaフォルダの下にmigration.sqlが作成されます。

prisma/migrations/~/migration.sql
-- CreateTable
CREATE TABLE `User` (
    `id` VARCHAR(191) NOT NULL,
    `firebaseUId` VARCHAR(191) NOT NULL,
    `name` VARCHAR(191) NOT NULL,
    `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `updatedAt` DATETIME(3) NOT NULL,

    UNIQUE INDEX `User_firebaseUId_key`(`firebaseUId`),
    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- CreateTable
CREATE TABLE `Todo` (
    `id` VARCHAR(191) NOT NULL,
    `userId` VARCHAR(191) NOT NULL,
    `title` VARCHAR(191) NOT NULL,
    `description` VARCHAR(191) NOT NULL,
    `status` ENUM('NOT_STARTED', 'IN_PROGRESS', 'COMPLETED') NOT NULL DEFAULT 'NOT_STARTED',
    `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `updatedAt` DATETIME(3) NOT NULL,

    PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `Todo` ADD CONSTRAINT `Todo_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;

prisma studioを使えばDBの状態を確認することができます。

ターミナル
npx prisma studio

localhost:5555

Prisma Clientのセットアップ

データベース構築までできるようになりました。
次はTypeScriptを使用してDB操作を行えるよう、Prisma Clientのセットアップを行います。
https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/generating-prisma-client
まずはPrisma Clientをインストールしましょう。

ターミナル
npm install @prisma/client

初回インストール時、npx prisma generateが自動で行われることでPrismaスキーマからnode_modules/.prisma/clientへコードが生成されます。
以降、スキーマの変更を行った場合はnpx prisma generateを再度行う必要があります。

Prisma Clientをインスタンス化できるようになったので、Nest.jsに組み込んでいきます。
src直下にprisma.service.tsファイルを作成しましょう。

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

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

これでPrismaServiceからDB操作のためのメソッドを呼び出すことができるようになりました。

オマケ

prismaが発行するSQLを見えるようにしておいた方が後々開発効率が上がるので、やっておきましょう。

prisma.service.ts
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Prisma, PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService
  extends PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel>
  implements OnModuleInit
{
  private readonly logger = new Logger(PrismaService.name);
  constructor() {
    super({ log: ['query', 'info', 'warn', 'error'] });
  }
  async onModuleInit(): Promise<void> {
    this.$on('query', (event) => {
      this.logger.log(
        `Query: ${event.query}`,
        `Params: ${event.params}`,
        `Duration: ${event.duration} ms`,
      );
    });
    this.$on('info', (event) => {
      this.logger.log(`message: ${event.message}`);
    });
    this.$on('error', (event) => {
      this.logger.log(`error: ${event.message}`);
    });
    this.$on('warn', (event) => {
      this.logger.log(`warn: ${event.message}`);
    });
    await this.$connect();
  }
}

以下を参考にしました。
https://zenn.dev/takepepe/articles/nestjs-prisma-logger
https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/databases-connections

GraphQL導入

それではgraphqlを導入していきます。スキーマファーストとコードファーストの2通りのやり方がありますが、今回はコードファーストでいきます。
https://docs.nestjs.com/graphql/quick-start

セットアップ

まず必要なパッケージをインストールします。

ターミナル
npm i @nestjs/graphql @nestjs/apollo @apollo/server graphql

次にgraphqlモジュールをNestJSアプリケーションに組み込み初期化します。

app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import * as path from 'path';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
   autoSchemaFile: path.join(process.cwd(), 'src/schema.gql'),
      sortSchema: true,
    }),
  ],
})
export class AppModule {}

autoSchemaFile: 自動生成されたスキーマが作成されるパス
sortSchema: デフォルトの場合、モジュールで定義された順にスキーマが並ぶ。trueにすると、辞書順に並ぶ。

これでgraphqlが使用できるようになりました!

アプリケーションを開発する準備が整ったので、次は機能を作っていきます。

https://zenn.dev/ouka031/articles/89eaaad8433743

Discussion