Turborepoを利用したmonorepo構成のNestJS+Prismaプロジェクトの構築

Turborepoを利用したmonorepo構成のNestJS+Prismaプロジェクトの構築

2023-01-0417 min read

目次

  1. 概要
  2. 初期化
  3. backend関連リポジトリの初期化
  4. primsa
  5. nestjs用-prismaservice
  6. 参考にしたサイト

概要

turborepoを利用したmonorepo構成のNestJS+Prismaプロジェクトを構築した際のメモです。

なぜ monorepo で管理するのか

Prismaを別のワークスペースに分けることで、フロントエンドのアプリケーションからPrismaのインタフェースをimportして共有したり、バックエンドの複数のリポジトリで単一のPrismaの設定を共有することに対応したかったからです。

turborepo について

Turborepo

Turborepoは Vercel が開発を行っている JS/TS のmonorepo環境のビルド管理システムです。

環境

  • npm 8.19.3
  • node 19.1.0
  • turbo 1.6.3

初期化

まずは以下のコマンドでたたき台を作成します。

npx create-turbo@latest
# ここではappという名前でプロジェクトを作成

デフォルトで次のようなアプリケーション・パッケージが作成されます。

  • apps/web: Next.js with TypeScript
  • apps/docs: Next.js with TypeScript
  • packages/ui: Shared React component library
  • packages/eslint-config-custom: Shared configuration (ESLint)
  • packages/tsconfig: Shared TypeScript tsconfig.json

Backend関連リポジトリの初期化

ここではサンプル用のAPIとしてNestJSとExpress.jsの2パターンのAPIの構築を実施します。

API1: NestJS API

NestJSは、Node.jsを使用したサーバサイドアプリケーション開発のためのフレームワークです。 モダンなJavaScriptの言語機能をフルに活用したアプリケーションを構築することができます。

npm workspaceのオプションを利用してNestJSのAPIのワークスペースを作成します。

npm init -y -w apps/nestjs-api
cd apps/nestjs-api
rm package.json # nest new コマンドを利用したいため削除
npx nest new .
# 質問にてpackage管理にnpmを選択
cd ../../
npm -w apps/nestjs-api run start  # 起動することを確認

このプロジェクトではルートディレクトリでnpm run devを行った場合に全てのリポジトリにてnpm run devが行われるようにしたいため、apps/nestjs-api/package.jsonを調整します

+    "dev": "nest start",
-    "start": "nest start",

API2: Express.js

Express.jsは軽量なWebアプリケーションフレームワークです。

npm init -y -w apps/express-api
npm -w apps/express-api install express --save

apps/express-api/index.js

const express = require('express');
const app = express();
const port = 3003;

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

apps/express-api/package.json

  "scripts": {
+    "dev": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Primsa

Prismaについて

データベースを操作するためのJavaScriptライブラリです。 ORM(Object-Relational Mapping)と呼ばれるもので、データベースに保存されているデータを、JavaScriptのオブジェクトとして扱えるようにするものです。

MySQLやPostgreSQL、SQLiteなどをはじめとするリレーショナルデータベース、MongoDBなどをはじめとするドキュメントデータベース、そしてGraphQLを使用したデータベースなどに対応しています。

packageとしてPrismaを追加

ここでは database という名前で package として Prisma を追加します。 以下のコマンドでワークスペースを構築します。

npm init -y -w packages/database
npm -w packages/database install @prisma/client
npm -w packages/database install prisma --save-dev
echo "public-hoist-pattern[]=*prisma*" >> .npmrc
cd packages/database
npx prisma init

schema.prismaにスキーマの定義を行います。ここではUsersテーブルを作成します。

packages/database/prisma/schema.prisma

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

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

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
}

prisma の generate や push に対応するコマンドを定義します。

packages/database/package.json

  "scripts": {
+    "db:generate": "prisma generate",
+    "db:push": "prisma db push --skip-generate",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

turbo.json

{
  "pipeline": {
+    "db:generate": {
+      "cache": false
+    },
+    "db:push": {
+      "cache": false
+    },
  }
}

最後に外のパッケージやアプリケーションでimportできるようexportの設定を定義します。

packages/database/index.ts

export * from '@prisma/client';
-  "main": "index.js",
+  "main": "./index.ts",
+  "types": "./index.ts"

NestJS用 PrismaService

NestJSでPrismaを利用するためにDIに対応するServiceクラスを作成します。 ここではこのクラスが複数のワークスペースで利用することを想定して、prisma-nestjs-pluginという名前で独立したパッケージとして作成します。

npm init -y -w packages/prisma-nestjs-plugin
npm -w packages/prisma-nestjs-plugin install @nestjs/common
mkdir -p packages/prisma-nestjs-plugin/src
touch packages/prisma-nestjs-plugin/src/index.ts
touch packages/prisma-nestjs-plugin/src/prisma.service.ts
touch packages/prisma-nestjs-plugin/src/prisma.module.ts
touch packages/prisma-nestjs-plugin/tsconfig.json

packages/prisma-nestjs-plugin/package.json

-  "main": "index.js",
+  "main": "./src/index.ts",
+  "types": "./src/index.ts",
+  "dependencies": {
+    "database": "*"
+  },

packages/prisma-nestjs-plugin/src/prisma.service.ts

import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from 'database';

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

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close();
    });
  }
}

packages/prisma-nestjs-plugin/src/prisma.module.ts

import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Module({
  imports: [],
  providers: [PrismaService],
})
export class PrismaModule {}

外部のアプリケーション・パッケージで利用できるようexportの設定を定義します。

packages/prisma-nestjs-plugin/src/index.ts

export * from './prisma.module';
export * from './prisma.service';

VSCodeでデコレータの警告が出力されるので、tsconfig の設定で許容させるようにします。

packages/prisma-nestjs-plugin/tsconfig.json

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

これでNestJSからPrismaを呼び出す準備が整いました。 試しにNestJSのアプリケーションからPrismaClientを呼び出す実装を書いてみます。

apps/nestjs-api/package.json

  "dependencies": {
+    "prisma-nestjs-plugin": "*",

apps/nestjs-api/src/app.module.ts

import { Module } from '@nestjs/common';
import { PrismaModule } from 'prisma-nestjs-plugin';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [PrismaModule], // PrismaModuleを追加
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

apps/nestjs-api/src/app.service.ts

import { Injectable } from '@nestjs/common';
// packages/prisma-nestjs-plugin を import
import { PrismaService } from 'prisma-nestjs-plugin';

@Injectable()
export class AppService {
  // PrismaServiceをコンストラクタの引数に設定
  constructor(private readonly prisma: PrismaService) {}

  getHello(): string {
    return 'Hello World!';
  }

  // Usersを取得するクエリ
  async getUsers() {
    return await this.prisma.user.findMany();
  }
}

ついでに apps/express-api/index.js の方にも /usersのエンドポイントを生やしておきます。

const { PrismaClient } = require('database');
const express = require('express');
const app = express();
const port = 3003;

const prisma = new PrismaClient();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.get('/users', async (req, res) => {
  const users = await prisma.user.findMany();
  res.send(users);
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

両方のAPIで動作確認ができたら構築は完了です。

参考にしたサイト

Tags
javascript(110)
node.js(54)
linux(54)
amazon%20aws(47)
typescript(45)
%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0(36)
%E7%94%BB%E5%83%8F%E5%87%A6%E7%90%86(30)
html5(29)
php(24)
centos(24)
python(22)
%E7%AB%B6%E6%8A%80%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0(21)
mac(21)
mysql(20)
canvas(19)
opencv(17)
%E9%9B%91%E8%AB%87(16)
docker(16)
wordpress(15)
atcoder(14)
apache(12)
%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92(12)
%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9(12)
amazon%20s3(12)
red%20hat(12)
prisma(12)
ubuntu(11)
github(10)
git(10)
vue.js(10)
%E7%94%BB%E5%83%8F%E5%87%A6%E7%90%86100%E6%9C%AC%E3%83%8E%E3%83%83%E3%82%AF(10)
mariadb(10)
react(9)
aws%20cdk(9)
css3(8)
%E5%8F%AF%E8%A6%96%E5%8C%96(8)
%E5%B0%8F%E3%83%8D%E3%82%BF(8)
nestjs(8)
amazon%20lightsail(7)
next.js(7)
%E3%83%96%E3%83%AD%E3%82%B0(6)
cms(6)
oracle(6)
perl(6)
gitlab(6)
iam(5)
amazon%20ec2(5)
%E8%B3%87%E6%A0%BC%E8%A9%A6%E9%A8%93(5)
aws%20amplify(5)
curl(4)
Author
githubzennqiita
ただの備忘録です。

※外部送信に関する公表事項