Prismaは、Node.js・TypeScript向けに設計された次世代のORM(Object-Relational Mapping)です。従来のORMとは異なるアプローチで型安全性を実現し、開発者体験を大幅に向上させます。本記事では、NestJSプロジェクトにPrismaを導入し、型安全なデータベースアクセスを実現する方法を実践的に解説します。

実行環境と前提条件

本記事の内容を実践するにあたり、以下の環境を前提としています。

項目 バージョン・要件
Node.js 20以上
npm 10以上
NestJS 11.x
Prisma 7.x
PostgreSQL 14以上(または MySQL 8.0以上 / SQLite)
OS Windows / macOS / Linux
エディタ VS Code(推奨)

事前に以下の準備を完了してください。

  • NestJS CLIのインストール済み(npm install -g @nestjs/cli
  • NestJSプロジェクトの作成済み(nest newコマンドで作成可能)
  • データベースサーバーが起動しており、接続可能な状態(本記事ではPostgreSQLを使用)

プロジェクトの作成方法はNestJS入門記事を参照してください。

Prismaとは

Prismaは、従来のORMとは異なる新しいアプローチでデータベース操作を行うツールです。Prismaスキーマという宣言的なデータモデル定義から、完全に型安全なクライアントコードを自動生成します。

Prismaの主要コンポーネント

Prismaは3つの主要コンポーネントで構成されています。

コンポーネント 役割
Prisma Schema データモデルを宣言的に定義するDSL(ドメイン固有言語)
Prisma Migrate スキーマ定義からSQLマイグレーションを生成・実行
Prisma Client スキーマから自動生成される型安全なデータベースクライアント

Prismaの主な特徴

特徴 説明
完全な型安全性 クエリ結果の型がコンパイル時に決定される
直感的なAPI findManycreateupdateなど分かりやすいメソッド名
リレーション操作 includeselectによる柔軟なリレーション取得
オートコンプリート VS Codeでのフィールド補完とエラー検出
Prisma Studio ブラウザベースのデータベース管理GUI

PrismaとTypeORMの比較

NestJSでよく使用されるTypeORMと比較して、Prismaの特徴を理解しましょう。

アーキテクチャの違い

観点 Prisma TypeORM
パターン DataMapper Active Record / DataMapper
モデル定義 独自スキーマファイル(.prisma) TypeScriptクラス + デコレータ
型生成 スキーマから自動生成 クラス定義がそのまま型になる
クエリビルダー 生成されたクライアントAPIを使用 QueryBuilderを手動で構築

型安全性の比較

TypeORMでは、selectオプションで特定のフィールドのみを取得しても、戻り値の型はエンティティ全体のままです。

1
2
3
4
5
6
7
// TypeORMの例:型安全性が不完全
const posts = await postRepository.find({
  select: ['id', 'title'],
});

// postsの型はPost[]だが、実際にはidとtitleしか含まれていない
console.log(posts[0].content); // 実行時エラー(undefinedにアクセス)

Prismaでは、クエリに応じて戻り値の型が動的に生成されます。

1
2
3
4
5
6
7
// Prismaの例:完全な型安全性
const posts = await prisma.post.findMany({
  select: { id: true, title: true },
});

// postsの型は { id: number; title: string }[] として推論される
console.log(posts[0].content); // コンパイルエラー(contentプロパティが存在しない)

フィルタリングの比較

TypeORMでは、フィルタ条件の型チェックが不完全な場合があります。

1
2
3
4
5
6
7
// TypeORMの例
const posts = await postRepository.find({
  where: {
    views: MoreThan('test'), // 文字列を渡してもコンパイルは通る
  },
});
// 実行時エラー: invalid input syntax for type integer

Prismaでは、フィルタ演算子も型安全です。

1
2
3
4
5
6
// Prismaの例
const posts = await prisma.post.findMany({
  where: {
    views: { gt: 'test' }, // コンパイルエラー: 型 'string' を型 'number' に割り当てることはできません
  },
});

ユースケース別の選択指針

ユースケース 推奨
新規TypeScriptプロジェクト Prisma
既存のTypeORMプロジェクトの保守 TypeORM継続
複雑なSQLが必要 TypeORM(QueryBuilderが強力)
型安全性を最優先 Prisma
Active Recordパターンを好む TypeORM

NestJSへのPrisma導入手順

1. Prisma CLIのインストール

開発依存としてPrisma CLIをインストールします。

1
npm install prisma --save-dev

2. Prismaの初期化

Prismaプロジェクトを初期化します。PostgreSQLを使用する場合は以下のコマンドを実行します。

1
npx prisma init --datasource-provider postgresql

このコマンドにより、以下のファイルが生成されます。

  • prisma/schema.prisma: データモデル定義ファイル
  • .env: データベース接続情報を記載する環境変数ファイル

3. 環境変数の設定

.envファイルにデータベース接続URLを設定します。

1
DATABASE_URL="postgresql://username:password@localhost:5432/nestjs_prisma?schema=public"

接続URLの形式はデータベースによって異なります。

データベース 接続URL形式
PostgreSQL postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA
MySQL mysql://USER:PASSWORD@HOST:PORT/DATABASE
SQLite file:./dev.db

4. Prismaスキーマの設定

prisma/schema.prismaを編集して、ジェネレータ設定を追加します。

generator client {
  provider     = "prisma-client"
  output       = "../src/generated/prisma"
  moduleFormat = "cjs"
}

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

moduleFormat = "cjs"の設定は、Prisma 7以降でNestJS(CommonJS)と互換性を保つために必要です。Prisma 7はデフォルトでESモジュールを生成しますが、NestJSはCommonJSを使用するため、この設定で明示的にCommonJSモジュールを生成します。

5. Prisma Clientのインストールと生成

Prisma Clientパッケージをインストールします。

1
npm install @prisma/client

PostgreSQLの場合は、ドライバアダプタもインストールします。

1
npm install @prisma/adapter-pg pg

Prisma Clientを生成します。

1
npx prisma generate

このコマンドにより、src/generated/prismaディレクトリに型定義を含むクライアントコードが生成されます。

Prismaスキーマの定義

基本的なモデル定義

ブログアプリケーションを例に、UserとPostモデルを定義します。

generator client {
  provider     = "prisma-client"
  output       = "../src/generated/prisma"
  moduleFormat = "cjs"
}

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

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  posts     Post[]
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

スキーマ定義の主要な要素

要素 説明
@id 主キーを指定 id Int @id
@default デフォルト値を設定 @default(autoincrement())
@unique ユニーク制約を付与 email String @unique
@relation リレーションを定義 @relation(fields: [authorId], references: [id])
@updatedAt 更新時に自動でタイムスタンプを更新 updatedAt DateTime @updatedAt
? NULLを許容 name String?

リレーションの種類

Prismaスキーマでは、以下のリレーションタイプをサポートしています。

// 1対多(One-to-Many)
model User {
  id    Int    @id @default(autoincrement())
  posts Post[]
}

model Post {
  id       Int  @id @default(autoincrement())
  author   User @relation(fields: [authorId], references: [id])
  authorId Int
}

// 多対多(Many-to-Many): 暗黙的
model Post {
  id         Int        @id @default(autoincrement())
  categories Category[]
}

model Category {
  id    Int    @id @default(autoincrement())
  posts Post[]
}

// 1対1(One-to-One)
model User {
  id      Int      @id @default(autoincrement())
  profile Profile?
}

model Profile {
  id     Int  @id @default(autoincrement())
  user   User @relation(fields: [userId], references: [id])
  userId Int  @unique
}

マイグレーションの実行

開発環境でのマイグレーション

スキーマ定義をデータベースに反映するには、Prisma Migrateを使用します。

1
npx prisma migrate dev --name init

このコマンドは以下の処理を実行します。

  1. スキーマの変更を検出
  2. SQLマイグレーションファイルを生成(prisma/migrations/配下)
  3. データベースにマイグレーションを適用
  4. Prisma Clientを再生成

生成されるマイグレーションファイルの例(prisma/migrations/20260106_init/migration.sql):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-- CreateTable
CREATE TABLE "User" (
    "id" SERIAL NOT NULL,
    "email" TEXT NOT NULL,
    "name" TEXT,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" TIMESTAMP(3) NOT NULL,

    CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Post" (
    "id" SERIAL NOT NULL,
    "title" TEXT NOT NULL,
    "content" TEXT,
    "published" BOOLEAN NOT NULL DEFAULT false,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    "updatedAt" TIMESTAMP(3) NOT NULL,
    "authorId" INTEGER NOT NULL,

    CONSTRAINT "Post_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

-- AddForeignKey
ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_fkey" 
  FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

本番環境でのマイグレーション

本番環境ではmigrate deployを使用します。

1
npx prisma migrate deploy

migrate devmigrate deployの違いは以下の通りです。

コマンド 用途 特徴
migrate dev 開発環境 マイグレーション生成 + 適用、シャドウDB使用
migrate deploy 本番環境 既存マイグレーションの適用のみ

PrismaServiceの実装

NestJSでPrismaを使用するには、PrismaServiceを作成してDI(依存性注入)できるようにします。

PrismaServiceの作成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '../generated/prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    const pool = new Pool({ connectionString: process.env.DATABASE_URL });
    const adapter = new PrismaPg(pool);
    super({ adapter });
  }

  async onModuleInit() {
    await this.$connect();
  }

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

SQLiteを使用する場合は、アダプタの設定が異なります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// SQLiteの場合
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    const adapter = new PrismaBetterSqlite3({ url: process.env.DATABASE_URL });
    super({ adapter });
  }
  // ...
}

PrismaModuleの作成

PrismaServiceを他のモジュールから利用できるように、専用のモジュールを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// src/prisma/prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

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

@Global()デコレータを付与することで、アプリケーション全体でPrismaServiceを利用可能にします。

AppModuleへの登録

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PrismaModule } from './prisma/prisma.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    PrismaModule,
  ],
})
export class AppModule {}

Prisma Clientの使い方

基本的なCRUD操作

レコードの作成(Create)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { User, Prisma } from '../generated/prisma';

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

  async createUser(data: Prisma.UserCreateInput): Promise<User> {
    return this.prisma.user.create({
      data,
    });
  }
}

使用例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 基本的な作成
const user = await this.usersService.createUser({
  email: 'alice@example.com',
  name: 'Alice',
});

// リレーションを含む作成(ネストされた書き込み)
const userWithPosts = await this.prisma.user.create({
  data: {
    email: 'bob@example.com',
    name: 'Bob',
    posts: {
      create: [
        { title: '最初の投稿', content: 'こんにちは!' },
        { title: '2番目の投稿', published: true },
      ],
    },
  },
  include: {
    posts: true,
  },
});

レコードの取得(Read)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 単一レコードの取得
async findUserById(id: number): Promise<User | null> {
  return this.prisma.user.findUnique({
    where: { id },
  });
}

// 複数レコードの取得
async findAllUsers(): Promise<User[]> {
  return this.prisma.user.findMany();
}

// 条件付き取得
async findUserByEmail(email: string): Promise<User | null> {
  return this.prisma.user.findUnique({
    where: { email },
  });
}

レコードの更新(Update)

1
2
3
4
5
6
async updateUser(id: number, data: Prisma.UserUpdateInput): Promise<User> {
  return this.prisma.user.update({
    where: { id },
    data,
  });
}

レコードの削除(Delete)

1
2
3
4
5
async deleteUser(id: number): Promise<User> {
  return this.prisma.user.delete({
    where: { id },
  });
}

リレーションの取得

includeによる関連データの取得

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ユーザーと投稿を一緒に取得
async findUserWithPosts(id: number) {
  return this.prisma.user.findUnique({
    where: { id },
    include: {
      posts: true,
    },
  });
}

// ネストされたinclude
async findUserWithPublishedPosts(id: number) {
  return this.prisma.user.findUnique({
    where: { id },
    include: {
      posts: {
        where: { published: true },
        orderBy: { createdAt: 'desc' },
      },
    },
  });
}

selectによるフィールド選択

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 特定のフィールドのみ取得
async findUserNames() {
  return this.prisma.user.findMany({
    select: {
      id: true,
      name: true,
      email: true,
    },
  });
}

// 戻り値の型は { id: number; name: string | null; email: string }[] として推論される

フィルタリングとソート

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 複合条件でのフィルタリング
async findPosts(params: {
  skip?: number;
  take?: number;
  where?: Prisma.PostWhereInput;
  orderBy?: Prisma.PostOrderByWithRelationInput;
}) {
  const { skip, take, where, orderBy } = params;
  return this.prisma.post.findMany({
    skip,
    take,
    where,
    orderBy,
  });
}

// 使用例
const publishedPosts = await this.findPosts({
  where: {
    published: true,
    title: { contains: 'NestJS' },
  },
  orderBy: { createdAt: 'desc' },
  take: 10,
});

Fluent APIによるリレーショントラバーサル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 投稿から著者を取得
async getPostAuthor(postId: number) {
  return this.prisma.post
    .findUnique({ where: { id: postId } })
    .author();
}

// ユーザーから投稿を取得
async getUserPosts(userId: number) {
  return this.prisma.user
    .findUnique({ where: { id: userId } })
    .posts();
}

実践的なサービス実装例

PostsServiceの完全な実装

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
// src/posts/posts.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { Post, Prisma } from '../generated/prisma';

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

  async create(data: Prisma.PostCreateInput): Promise<Post> {
    return this.prisma.post.create({
      data,
      include: { author: true },
    });
  }

  async findAll(params: {
    skip?: number;
    take?: number;
    cursor?: Prisma.PostWhereUniqueInput;
    where?: Prisma.PostWhereInput;
    orderBy?: Prisma.PostOrderByWithRelationInput;
  }): Promise<Post[]> {
    const { skip, take, cursor, where, orderBy } = params;
    return this.prisma.post.findMany({
      skip,
      take,
      cursor,
      where,
      orderBy,
      include: { author: true },
    });
  }

  async findOne(id: number): Promise<Post> {
    const post = await this.prisma.post.findUnique({
      where: { id },
      include: { author: true },
    });

    if (!post) {
      throw new NotFoundException(`Post with ID ${id} not found`);
    }

    return post;
  }

  async update(id: number, data: Prisma.PostUpdateInput): Promise<Post> {
    try {
      return await this.prisma.post.update({
        where: { id },
        data,
        include: { author: true },
      });
    } catch (error) {
      if (error instanceof Prisma.PrismaClientKnownRequestError) {
        if (error.code === 'P2025') {
          throw new NotFoundException(`Post with ID ${id} not found`);
        }
      }
      throw error;
    }
  }

  async remove(id: number): Promise<Post> {
    try {
      return await this.prisma.post.delete({
        where: { id },
      });
    } catch (error) {
      if (error instanceof Prisma.PrismaClientKnownRequestError) {
        if (error.code === 'P2025') {
          throw new NotFoundException(`Post with ID ${id} not found`);
        }
      }
      throw error;
    }
  }

  async publish(id: number): Promise<Post> {
    return this.prisma.post.update({
      where: { id },
      data: { published: true },
    });
  }

  async findPublished(): Promise<Post[]> {
    return this.prisma.post.findMany({
      where: { published: true },
      include: { author: true },
      orderBy: { createdAt: 'desc' },
    });
  }

  async search(searchString: string): Promise<Post[]> {
    return this.prisma.post.findMany({
      where: {
        OR: [
          { title: { contains: searchString, mode: 'insensitive' } },
          { content: { contains: searchString, mode: 'insensitive' } },
        ],
      },
      include: { author: true },
    });
  }
}

コントローラの実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// src/posts/posts.controller.ts
import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  Query,
  ParseIntPipe,
} from '@nestjs/common';
import { PostsService } from './posts.service';
import { Prisma } from '../generated/prisma';

@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Post()
  create(@Body() createPostDto: Prisma.PostCreateInput) {
    return this.postsService.create(createPostDto);
  }

  @Get()
  findAll(
    @Query('skip') skip?: string,
    @Query('take') take?: string,
    @Query('published') published?: string,
  ) {
    return this.postsService.findAll({
      skip: skip ? parseInt(skip, 10) : undefined,
      take: take ? parseInt(take, 10) : undefined,
      where: published !== undefined ? { published: published === 'true' } : undefined,
      orderBy: { createdAt: 'desc' },
    });
  }

  @Get('published')
  findPublished() {
    return this.postsService.findPublished();
  }

  @Get('search')
  search(@Query('q') query: string) {
    return this.postsService.search(query);
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.postsService.findOne(id);
  }

  @Patch(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() updatePostDto: Prisma.PostUpdateInput,
  ) {
    return this.postsService.update(id, updatePostDto);
  }

  @Patch(':id/publish')
  publish(@Param('id', ParseIntPipe) id: number) {
    return this.postsService.publish(id);
  }

  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number) {
    return this.postsService.remove(id);
  }
}

トランザクションの使用

Prismaでは、複数のデータベース操作をアトミックに実行するためのトランザクションをサポートしています。

インタラクティブトランザクション

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
async transferPost(postId: number, newAuthorId: number) {
  return this.prisma.$transaction(async (tx) => {
    // 投稿が存在するか確認
    const post = await tx.post.findUnique({
      where: { id: postId },
    });

    if (!post) {
      throw new NotFoundException('Post not found');
    }

    // 新しい著者が存在するか確認
    const newAuthor = await tx.user.findUnique({
      where: { id: newAuthorId },
    });

    if (!newAuthor) {
      throw new NotFoundException('New author not found');
    }

    // 投稿の著者を更新
    return tx.post.update({
      where: { id: postId },
      data: { authorId: newAuthorId },
      include: { author: true },
    });
  });
}

バッチトランザクション

複数の独立した操作をまとめて実行する場合は、配列形式のトランザクションを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
async createUserWithInitialPost(userData: Prisma.UserCreateInput, postTitle: string) {
  const [user, post] = await this.prisma.$transaction([
    this.prisma.user.create({ data: userData }),
    this.prisma.post.create({
      data: {
        title: postTitle,
        author: { connect: { email: userData.email } },
      },
    }),
  ]);

  return { user, post };
}

Prisma Studioの活用

Prisma Studioは、ブラウザベースのデータベース管理GUIです。開発中のデータ確認やデバッグに便利です。

1
npx prisma studio

このコマンドを実行すると、ブラウザが開き、データベースのテーブル一覧とレコードを視覚的に確認・編集できます。

Prisma Studioで可能な操作:

  • レコードの閲覧・検索
  • レコードの作成・更新・削除
  • リレーションの確認
  • フィルタリングとソート

期待される結果

本記事の手順を完了すると、以下の状態が達成されます。

  1. Prismaの環境構築完了: prisma/schema.prismaでモデルが定義され、src/generated/prismaに型安全なクライアントが生成されている
  2. データベースとの接続確立: マイグレーションが適用され、テーブルが作成されている
  3. NestJSとの統合: PrismaServicePrismaModuleにより、任意のサービスからPrisma Clientを利用可能
  4. 型安全なCRUD操作: コンパイル時に型エラーを検出でき、オートコンプリートが有効

動作確認として、アプリケーションを起動してAPIエンドポイントをテストします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# アプリケーションの起動
npm run start:dev

# ユーザーの作成
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "name": "Test User"}'

# 投稿の作成
curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "Hello Prisma", "content": "型安全なORM", "author": {"connect": {"email": "test@example.com"}}}'

まとめ

本記事では、NestJSプロジェクトにPrismaを導入し、型安全なデータベースアクセスを実現する方法を解説しました。

Prismaの主なメリットをまとめると以下の通りです。

  • 完全な型安全性: クエリ結果の型がコンパイル時に決定され、ランタイムエラーを防止
  • 優れた開発者体験: オートコンプリート、エラー検出、Prisma Studioによるデータ管理
  • 宣言的なスキーマ定義: Prismaスキーマによる直感的なモデル定義とマイグレーション管理
  • NestJSとの親和性: DIパターンとの自然な統合

TypeORMからの移行を検討している場合は、段階的な移行が可能です。まずは新しい機能からPrismaを導入し、徐々に既存のTypeORMコードを置き換えていくアプローチが推奨されます。

参考リンク