🏢

Turborepo/Vite/Nest.js/Prismaでプロジェクトを構築する

に公開

これは何?

React Advent Calendar 2025の1日目の記事です。

React(どころかTypeScript)経験ゼロの私がある日突然、開発案件に入り、既存の案件を参考にReactの開発環境構築を求められました。でも、既存の案件ではゼロから環境構築する手順は残っていない...どうしたらいいんだ...という過去の自分へのメモです。

環境

dockerfile
FROM debian:trixie-slim

RUN apt update && apt install -y \
    curl \
    xz-utils \
    libatomic1

WORKDIR /app
docker-compose.yml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 5173:5173
    volumes:
      - .:/apps
    stdin_open: true
    tty: true
  database:
    image: postgres:18.1-trixie
    container_name: my-project-postgres18
    ports:
      - 5432:5432
    volumes:
      - postgres_data:/var/lib/postgresql/18/docker
      - ./init:/docker-entrypoint-initdb.d
    environment:
      POSTGRES_USER: hoge
      POSTGRES_PASSWORD: hoge
      POSTGRES_DB: my_project_db
    restart: always
volumes:
  postgres_data:

PostgreSQL 18でvolumesのマウントパスが変わったようなので注意。

環境構築手順

protoとNode.js/pnpmのインストール

なにはともあれNode.jsのインストールから...と過去の案件を確認したところVoltaというバージョン管理マネージャを使っていました。ただ、調べると最近はprotoがいいよ、という情報もあって、悩んだ末にprotoを採用しました。

bash <(curl -k https://moonrepo.dev/install/proto.sh | sed 's/curl --/curl -k --/g')

以下の内容が表示されたらスペースキーを押してEnterキーを押します(地味にわかりにくい)。

Which profile or config file to update?
│
│ ❯ /root/.bashrc
│   /root/.profile
│   Other
│   None
│
│ ⎵ select ⁃ ↕ cycle ⁃ ↵ submit

このままNode.jsをインストール...と思ったら、一度ターミナルを閉じて新たなターミナルを起動しないとダメでした。

あと、npmを入れようとしましたが、諸々ブラッシュアップされてそうな後発のpnpmを選びました。

protoコマンドでNode.jsとpnpmをバージョン指定(pin)してインストールします。

proto install node 25.2.1 --pin
proto install pnpm 10.24 --pin

次の内容を含んだ.prototoolsというファイルができます。

node = "25.2.1"
pnpm = "10.24.0"

このファイルをGitHubでコミットしておけば、他の開発者は

proto install

とするだけで固定されたバージョンのものがインストールされます。

Turborepoによるプロジェクト生成

既存の案件ではフロントエンドとバックエンドでプロジェクトが分かれていました。「Turborepo」というモノレポツールを使っていたので踏襲しました。並行している案件ではNxを採用していたのですが、ちょっと追い付かず...。

pnpx create-turbo@latest

ルートプロジェクトの名前をここでは「my-project」として進めます。

Packages: +92
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 92, reused 0, downloaded 92, added 92, done
? Where would you like to create your Turborepo? my-project
? Which package manager do you want to use? pnpm

>>> Creating a new Turborepo with:

Application packages
 - apps/docs
 - apps/web
Library packages
 - packages/eslint-config
 - packages/typescript-config
 - packages/ui

>>> Success! Created your Turborepo at my-project

To get started:
- Change to the directory: cd my-project
- Enable Remote Caching (recommended): pnpm dlx turbo login
   - Learn more: https://turborepo.com/remote-cache

- Run commands with Turborepo:
   - pnpm run build: Build all apps and packages
   - pnpm run dev: Develop all apps and packages
   - pnpm run lint: Lint all apps and packages
- Run a command twice to hit cache

プロジェクトファイルの作成

サンプルプロジェクトで生成されるapps/webとapps/docsは削除します。

rm -rf my-project/apps/web/ my-project/apps/docs/

package.jsonにpnpmとnodeのバージョンをprotoで固定したバージョンに揃えます。

package.json
  "packageManager": "pnpm@10.24.0",
  "engines": {
    "node": "25.2.1"
  }

フロントエンドの構築

Viteによるプロジェクト作成

https://oukayuka.booth.pm/items/2368019

Turborepoで作成されたプロジェクト配下のappsに移動して、Viteのプロジェクトを作成します。

cd my-project/apps
pnpm create vite@latest

コンソールの表示にしたがってプロジェクト名や利用するフレームワークを選択します。ここではプロジェクト名を「frontend」にして書いていきます。フレームワークはReactを選択。今回、画面遷移の部分は書きませんが、プロジェクトではReact Routerを採用したので、Select a variantでReact Router v7を選んだ流れにしています。

> npx
> "create-vite"

│
◇  Project name:
│  frontend
│
◇  Select a framework:
│  React
│
◇  Select a variant:
│  React Router v7 ↗
│
◇  Use rolldown-vite (Experimental)?:
│  No
│
◇  Install with pnpm and start now?
│  Yes

> npx
> create-react-router frontend


         create-react-router v7.9.6
      ◼  Directory:
         Using frontend as project directory

      ◼  Using default template
         See https://github.com/remix-run/react-router-templates for more
      ✔  Template copied

   git   Initialize a new git repository?
         No

  deps   Install dependencies with pnpm?
         Yes

      ✔  Dependencies installed

  done   That's it!

 Enter your project directory using cd ./frontend
 Check out README.md for development and deploy instructions.

 Join the community at https://rmx.as/discord

Viteのプロジェクトを次のコマンドで起動して、Webブラウザから http://localhost:5173/ にアクセスします。

cd frontend
pnpx vite --host

以下の画面が表示されたらOKです。

バックエンドの構築

Nest.jsによるプロジェクト作成

こちらも既存案件の踏襲なんですがNest.jsを使ってAPI部分を作成します。プロジェクト名は「backend」にしました。

pnpx @nestjs/cli new backend
Packages: +213
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 213, reused 63, downloaded 150, added 213, done
✨  We will scaffold your app in a few seconds..

✔ Which package manager would you ❤️  to use? pnpm
CREATE backend/.prettierrc (52 bytes)
CREATE backend/README.md (5036 bytes)
CREATE backend/eslint.config.mjs (899 bytes)
CREATE backend/nest-cli.json (171 bytes)
CREATE backend/package.json (1978 bytes)
CREATE backend/tsconfig.build.json (97 bytes)
CREATE backend/tsconfig.json (677 bytes)
CREATE backend/src/app.controller.ts (274 bytes)
CREATE backend/src/app.module.ts (249 bytes)
CREATE backend/src/app.service.ts (142 bytes)
CREATE backend/src/main.ts (228 bytes)
CREATE backend/src/app.controller.spec.ts (617 bytes)
CREATE backend/test/jest-e2e.json (183 bytes)
CREATE backend/test/app.e2e-spec.ts (669 bytes)

✔ Installation in progress... ☕

🚀  Successfully created project backend
👉  Get started with the following commands:

$ cd backend
$ pnpm run start


Failed to execute command: git git init
Git repository has not been initialized
                                         
                          Thanks for installing Nest 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.
                                         
                                         
               🍷  Donate: https://opencollective.com/nest

Prisma 6では特に設定が不要でしたが、Prisma 7になってdriver adapterなるものが必要になったようなのでこちらを参考にadapter-pgを入れました。

pnpm install @prisma/adapter-pg

Prisma

ORMも既存案件の踏襲でPrismaを採用しました。個人的にはDrizzle辺りも気になる...のですが、学習時間がない(-_-;

Turborepoを利用しているので、こちらを参考にしました。

まずはpackagesフォルダ配下にdatabaseフォルダを作成して、package.jsonを置きます。

cd database
touch package.json

package.jsonの内容は次のとおりです。

{
    "name": "@repo/database",
    "version": "0.0.0"
}

Prismaをインストールします。今のプロジェクトではPostgreSQLを利用するのでextension-accelerateも入れました。

pnpm add prisma --save-dev
pnpm add @prisma/client
pnpm add @prisma/extension-accelerate

続けてPrismaClientをbackendのプロジェクトで参照できるように次のコマンドを実行。

pnpx prisma init --output ../../../node_modules/.prisma/client

generator clientの部分はPrisma 7になってprisma-client-jsではなくprisma-clientになるようですが、動かなかったので一旦prisma-client-jsで書きましたorz

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"
  output   = "../../../node_modules/.prisma/client"
}

datasource db {
  provider = "postgresql"
}

model users {
  id    Int     @id @default(autoincrement())
  name  String
}

package.jsonにprismaのコマンドを追加します。

my-project/packages/database/package.json
"scripts": {
    "db:generate": "prisma generate",
    "db:migrate": "prisma migrate dev",
    "db:deploy": "prisma migrate deploy"
},

Turborepoでビルドできるようにturbo.jsonに以下を追加します。

turbo.json
"db:generate": {
  "cache": false
},
"db:migrate": {
  "cache": false,
  "persistent": true
},
"db:deploy": {
  "cache": false
}

generateとmigrateを実行。

pnpm turbo db:generate
turbo 2.6.1

• Packages in scope: @repo/database, @repo/eslint-config, @repo/typescript-config, @repo/ui, backend, frontend
• Running db:generate in 6 packages
• Remote caching disabled
┌─ @repo/database#db:generate > cache bypass, force executing e3c3b0be56aa324f 


> @repo/database@0.0.0 db:generate /apps/my-project/packages/database
> prisma generate

Loaded Prisma config from prisma.config.ts.

Prisma schema loaded from prisma/schema.prisma


✔ Generated Prisma Client (v7.0.1) to ./../../node_modules/.prisma/client in
 330ms

Start by importing your Prisma Client (See: https://pris.ly/d/importing-clie
nt)

Tip: Need your database queries to be 1000x faster? Accelerate offers you th
at and more: https://pris.ly/tip-2-accelerate
└─ @repo/database#db:generate ──

 Tasks:    1 successful, 1 total
Cached:    0 cached, 1 total
  Time:    44.158s 
pnpm turbo db:migrate
turbo 2.6.1

• Packages in scope: @repo/database, @repo/eslint-config, @repo/typescript-config, @repo/ui, backend, frontend
• Running db:migrate in 6 packages
• Remote caching disabled
┌─ @repo/database#db:migrate > cache bypass, force executing d08941c40824c041 

> @repo/database@0.0.0 db:migrate /apps/my-project/packages/database
> prisma migrate dev --schema=./prisma/schema.prisma

Loaded Prisma config from prisma.config.ts.

Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "my_project_db", schema "public" at "da
tabase:5432"

Applying migration `20251130105035_first`

The following migration(s) have been applied:

migrations/
  └─ 20251130105035_first/
    └─ migration.sql

Your database is now in sync with your schema.
└─ @repo/database#db:migrate ──

 Tasks:    1 successful, 1 total
Cached:    0 cached, 1 total
  Time:    43.485s

PostgreSQLにusersテーブルができたことを確認して、レコードを登録します。

psql -h localhost -p 5432 -U hoge -d my_project_db
psql (18.1 (Debian 18.1-1.pgdg13+2))
Type "help" for help.

my_project_db=# \dt
               List of tables
 Schema |        Name        | Type  | Owner 
--------+--------------------+-------+-------
 public | _prisma_migrations | table | hoge
 public | users              | table | hoge
(2 rows)

my_project_db=# insert into users (name) values ('佐々木舞香');
INSERT 0 1

最後に、backendでデフォルトで生成されたapp.controller.tsを書き換えました。

app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { PrismaClient } from ".prisma/client"
import { PrismaPg } from '@prisma/adapter-pg'

const adapter = new PrismaPg({ 
  connectionString: "postgresql://hoge:hoge@database:5432/my_project_db"
});
const prisma = new PrismaClient({adapter});

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    if(prisma === null){
      console.log("Prisma client is null");
    } else {
      prisma.users.findMany().then(users => {
        console.log(users);
      });
    }
    return this.appService.getHello();
  }
}

https://localhost:3000にアクセスして、レコードが表示されることを確認します。

pnpm run start

> backend@0.0.1 start /apps/my-project/apps/backend
> nest start

[Nest] 83310  - 12/01/2025, 12:37:33 PM     LOG [NestFactory] Starting Nest application...
[Nest] 83310  - 12/01/2025, 12:37:33 PM     LOG [InstanceLoader] AppModule dependencies initialized +8ms
[Nest] 83310  - 12/01/2025, 12:37:33 PM     LOG [RoutesResolver] AppController {/}: +8ms
[Nest] 83310  - 12/01/2025, 12:37:33 PM     LOG [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 83310  - 12/01/2025, 12:37:33 PM     LOG [NestApplication] Nest application successfully started +1ms
[ { id: 1, name: '佐々木舞香' } ]

以上です。

Discussion