NestJSで記事投稿APIを作ろう ─ Prisma導入とCRUD実装の基本

  • nestjs
    nestjs
  • prisma
    prisma
  • postgresql
    postgresql
  • docker
    docker
2025/04/12に公開

はじめに

本記事では、NestJSとPrismaを用いて記事投稿APIを実装する方法を、基本から一歩ずつ丁寧に解説していきます。

NestJSは、高い拡張性と保守性を誇るフレームワークとして注目を集めており、中〜大規模アプリケーションにおいても安心して採用できる技術です。そしてPrismaは、型安全かつ直感的な操作が可能なORMとして、TypeScriptとの相性が非常に良く、近年多くのプロジェクトで採用されています。

想定読者

本記事は、NestJSの基本的な構造をある程度理解している方を対象としています。とくに、

  • NestJSの基本的なプロジェクト構成は知っているが、実用的なAPIの組み方に慣れていない方
  • Prismaを使ってみたいが、実際のAPI開発にどう落とし込むかを体感したい方
  • 単なる動作確認ではなく、「現場で役立つ」APIの構成を学びたい方

https://shinagawa-web.com/blogs/nestjs-blog-series-setup-and-config

今回のゴール

本記事では、次のような成果物を目指します。

  • Prismaを導入し、記事(Post)モデルを定義・マイグレーションできるようになる
  • NestJS上に記事投稿に関するCRUD API(一覧取得、投稿、詳細取得、更新、削除)を構築する
  • バリデーションやDTO設計を取り入れ、堅牢な構成で実装できる

この構成を押さえておくことで、実際のプロダクト開発にも耐えられる「現場対応力」が上がるはずです。

PostmanによるAPIテストで実際に記事の投稿や参照ができるところまで実装します。

Image from Gyazo

この連載の全体像と今回の位置づけ

連載構成

  1. NestJSで始めるWebアプリ開発 ─ ブログサイトを作りながら学ぶプロジェクト構成と設定管理
  2. NestJSで記事投稿APIを作ろう ─ Prisma導入とCRUD実装の基本 ← 今回の記事
  3. アプリの信頼性を高める ─ ロギング・エラーハンドリング・テスト戦略
  4. PrismaとDB設計を深掘る ─ モデル・リレーション・運用設計
  5. ReactでUIを構築して本番へデプロイ ─ Dockerと環境構築

https://shinagawa-web.com/blogs/nestjs-blog-series-setup-and-config

https://shinagawa-web.com/blogs/nestjs-blog-series-logging-error-testing

https://shinagawa-web.com/blogs/nestjs-blog-series-prisma-db-design

https://shinagawa-web.com/blogs/nestjs-blog-series-react-deploy

DockerでPostgreSQLを立てる

まずは、NestJS + Prisma が接続できるように、Docker上にPostgreSQLを立てます。

docker-compose.yml をプロジェクト直下に作成

docker-compose.yml
services:
  db:
    image: postgres:15
    container_name: nestjs-postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: nestjs_blog
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:
  • ボリュームで永続化しておけば、コンテナの再起動でもデータが消えません

起動コマンド

バックグラウンドでコンテナが立ち上がります。

docker-compose up -d

DB接続確認

docker exec -it nestjs-postgres psql -U postgres -d nestjs_blog

コンテナに入れたらデータベース一覧を確認します。

\l

nestjs_blog が表示されればOKです。

Image from Gyazo

コンテナに直接入って作業を行うことは特にないので、いったん抜けます。

\q

.env に接続情報を記述

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/nestjs_blog"

この接続文字列で、Prisma ClientがDocker上のPostgreSQLにアクセス可能になります。

Prismaの導入と初期設定

記事投稿APIの基盤となるORM(Object-Relational Mapping)として、今回は Prisma を採用します。Prismaは、TypeScriptと強く結びついたモダンなORMで、型安全かつ直感的にDB操作ができるのが大きな特徴です。

まずは、NestJSプロジェクトにPrismaを導入し、データベースとの接続設定を整えていきましょう。

https://www.prisma.io/

Prismaのインストール

まずは、必要なパッケージをインストールします。NestJSプロジェクトのルートで、以下のコマンドを実行してください。

cd backend
npm install prisma --save-dev
npm install @prisma/client
  • prisma はCLIやマイグレーション用のツール
  • @prisma/client は、アプリケーションからDB操作を行うためのライブラリです

prisma init の実行

次に、Prismaの初期化コマンドを実行します。

npx prisma init
  • prisma/schema.prisma:モデル定義ファイルが作成されます。
prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
  output   = "../generated/prisma"
}

このように設定されていると、後ほど実施するPrisma Clientの生成で出力先が/generateとなりNestJSで正しく認識できなくなるため削除します。

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
- output   = "../generated/prisma"
}

モデル定義:記事(Post)モデルの作成

Prismaの導入と接続設定が完了したので、次は記事投稿用のモデルを定義します。

ここでは、Post という名前のモデルを作成し、記事投稿に必要なフィールドを設計します。記事にはタイトル、本文、作成日時、更新日時などが含まれる想定です。

schema.prismaの編集

backend/prisma/schema.prisma を以下のように編集します。

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

この定義により、Post モデルには以下のフィールドが含まれます

フィールド名 補足
id Int 主キー。自動で連番が振られます
title String 記事のタイトル
content String 記事の本文
createdAt DateTime 作成日時。自動的に現在時刻を設定
updatedAt DateTime 更新日時。レコード更新時に自動更新

Prismaでは @default@updatedAt といったアノテーションで、フィールドの初期値や自動更新の挙動を定義できます。

マイグレーションの実行

モデルを定義したら、データベースにテーブルを作成するためのマイグレーションを実行します。

cd backend
npx prisma migrate dev --name init

Image from Gyazo

このコマンドを実行すると、以下のような処理が行われます。

  • migrations/ ディレクトリにマイグレーション履歴が生成されます
  • Prismaのモデル定義に基づいて、PostgreSQL上に Post テーブルが作成されます
  • @prisma/client が自動的に再生成され、型安全なDBアクセスが可能になります

テーブル作成の確認(Prisma Studio を使用)

マイグレーションが完了したので、Prisma Studioを使ってテーブルの作成状況を確認します。

Prisma Studioは、Prismaが提供しているWebベースのデータブラウザで、ローカルDBの中身をGUIで確認できる便利なツールです。

以下のコマンドで起動します。

npx prisma studio

Image from Gyazo

コマンドを実行すると、ブラウザが起動し、Post テーブルが表示されていることを確認できます。
テーブル名が左サイドバーに表示され、クリックすることで中身の確認やレコードの追加・編集も可能です。

GUIで確認できるので、マイグレーションが正しく適用されたかを直感的に把握できます。

CLIでの確認ももちろん可能ですが、作業効率と視認性を優先して、今後はPrisma Studioでの確認を基本とすることにします。

次は、この Post モデルを使って、実際にデータを読み書きするためのAPI(CRUD処理)を実装していきます。モデルとテーブルが整ったことで、バックエンドとしての骨格が見えてきました。

Prisma Clientの生成とNestJSへの統合

記事モデルを定義し、マイグレーションによってデータベースにテーブルを作成したところまでで、データ構造は整いました。
次に行うのは、そのデータベースにアクセスするための仕組みを用意することです。

Prisma Clientとは?

Prismaでは、schema.prisma に書いたモデル定義から、Prisma Client というクラスが自動生成されます。これは、データベースにアクセスするためのコードです。

prisma.post.findMany() // Postテーブルの全件取得
prisma.post.create({ data: { title, content } }) // 新規レコード作成

といった形で、型安全にDB操作が行えるクライアントライブラリになります。

このPrisma Clientは @prisma/client パッケージの中に生成され、NestJSからも利用できるようになります。

Prisma Clientの生成

以下のコマンドで生成できます。

cd backend
npx prisma generate

生成されるファイルは node_modules/@prisma/client に含まれ、Prismaの型情報もここに定義されます。

NestJSでPrismaを使うには?

NestJSでは、直接 new PrismaClient() を呼ぶよりも、NestJSの「依存性注入(DI)」の仕組みを使って各所で共有するのが一般的です。

そのために、Prisma ClientをラップしたPrismaService を作成します。

PrismaServiceとは?

PrismaServiceは、以下のような役割を担うクラスです。

  • PrismaClientを継承し、アプリ全体で使い回せるようにする
  • NestJSの「サービス」として登録し、Controllerや他のServiceで使えるようにする
  • アプリ起動時にDBへ接続し、終了時には切断する
backend/src/prisma/prisma.service.ts
import { PrismaClient } from '@prisma/client';
import { Injectable } from '@nestjs/common';
import { OnModuleDestroy, OnModuleInit } from '@nestjs/common/interfaces/hooks';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect(); // アプリ起動時にDB接続
  }

  async onModuleDestroy() {
    await this.$disconnect(); // アプリ終了時に切断
  }
}

これによって、アプリケーション全体で一つの PrismaService を使いまわせるようになります。

PrismaService を提供するためのモジュールを作成し、それを必要な場所で import することで、使いたい場所でPrismaの機能を使えるようにするというのが、NestJSの設計思想です。

backend/src/prisma/prisma.module.ts
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Module({
  providers: [PrismaService],
  exports: [PrismaService], // 他のモジュールから使えるようにする
})
export class PrismaModule {}

これをこのあと作成する PostModule や AppModule に imports: [PrismaModule] で読み込むことで、NestJSの「依存性注入」で PrismaService がどこでも使えるようになります。

ここまでのまとめ

概念 役割
Prisma Client Prismaが生成する、型付きのDB操作ライブラリ
PrismaService NestJS内でPrisma Clientを共通管理するサービス(接続・切断・注入管理)
PrismaModule PrismaServiceを提供・共有するNestJSモジュール

CRUD APIを作成(Controller / Service の実装)

Post モデルの定義とテーブルの作成が完了したので、続いては記事投稿に関するAPIのCRUD機能を実装していきます。
ここでは、NestJSのControllerとServiceを用いて、以下の5つのエンドポイントを構築します。

  • 記事一覧の取得(GET /posts
  • 新規記事の作成(POST /posts
  • 記事の詳細取得(GET /posts/:id
  • 記事の更新(PATCH /posts/:id
  • 記事の削除(DELETE /posts/:id

Prisma Clientを使用して、これらの操作をすべて型安全に実行できるようにします。

PostModuleの作成

まずは記事用のモジュール、コントローラ、サービスを一括で作成します。

cd backend
npx nest g resource posts

Image from Gyazo

これにより、src/posts/ 配下に必要なファイルが自動生成されました。

  • posts.controller.ts
  • posts.service.ts
  • posts.module.ts
  • dto/create-post.dto.ts
  • dto/update-post.dto.ts

この段階で、簡易的なCRUD APIの雛形が整っています。

DTOの型とモデルを一致させる

DTOが PostCreateInput に対応している必要がありますので以下のように修正します。

backend/src/posts/dto/create-post.dto.ts
export class CreatePostDto {
+ title: string;
+ content: string;
}

DTOに関する詳しい解説は後ほどご紹介します。

PrismaServiceの注入

PostService では、データベース操作に PrismaService を利用します。
NestJSではサービスの依存性を注入するため、PostService のコンストラクタで以下のように書きます。

backend/src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { PrismaService } from 'src/prisma/prisma.service';

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

  create(createPostDto: CreatePostDto) {
    return this.prisma.post.create({ data: createPostDto });
  }

  findAll() {
    return this.prisma.post.findMany();
  }

  findOne(id: number) {
    return this.prisma.post.findUnique({ where: { id } });
  }

  update(id: number, updatePostDto: UpdatePostDto) {
    return this.prisma.post.update({
      where: { id },
      data: updatePostDto,
    });
  }

  remove(id: number) {
    return this.prisma.post.delete({ where: { id } });
  }
}

※差分が多いため最終的なコードを載せておきます。

NestJSでは、サービスを注入するには、モジュールでそのサービスを「提供(provide)」する必要があります。
今回は、PrismaServicePrismaModule で提供・エクスポートする構成を取りました。

backend/src/posts/posts.module.ts
import { Module } from '@nestjs/common';
import { PostService } from './post.service';
import { PostController } from './post.controller';
+ import { PrismaModule } from 'src/prisma/prisma.module';

@Module({
+ imports: [PrismaModule],
  controllers: [PostController],

PostmanによるAPIテスト

コードの修正が終わったら、NestJSを起動します。

cd backend
npm run start:dev

今回はPostManを使ってAPIにアクセスし投稿の登録や参照ができるか確認します。

https://www.postman.com/

PostmanのVSCode拡張があるので私はこちらを使って動作確認を行います。

https://marketplace.visualstudio.com/items?itemName=Postman.postman-for-vscode

① 新規記事を作成(POST /posts)

{
  "title": "はじめての記事",
  "content": "これはNestJS + Prismaで作ったAPIの最初の投稿です。"
}

リクエストが成功すれば、ステータス 201 Created と作成された記事のJSONが返ってきます。

Image from Gyazo

② 記事一覧を取得(GET /posts)

作成した記事が一覧で返ってきます(配列で)。

Image from Gyazo

③ 記事詳細を取得(GET /posts/:id)

IDに応じて、特定の記事を1件だけ取得できます。

Image from Gyazo

④ 記事を更新(PATCH /posts/:id)

{
  "title": "記事タイトルを更新しました"
}

部分更新も可能です。レスポンスに更新後のデータが返ります。

Image from Gyazo

⑤ 記事を削除(DELETE /posts/:id)

成功すればステータス 200 OK と削除された記事の情報が返ります。

Image from Gyazo

その後、再度記事一覧を取得を実行すると1件も返ってこないことを確認できます。

Image from Gyazo

バリデーションとDTOの設計

記事投稿APIに対して、正しいデータだけが送られてくるようにするために、DTOとバリデーションを設計します。
NestJSでは、DTOに class-validator を使ったデコレーターを付けることで、バリデーションロジックを簡潔に実装できます。

DTOとは何か?

DTO(Data Transfer Object)は、リクエストやレスポンスの構造を明示するための型定義です。
NestJSでは、DTOをクラスで定義することで、TypeScriptの型チェックに加え、バリデーションの対象としても機能します。

たとえば、記事の作成時に必要なデータ構造は以下のように設計します。

パッケージのインポート

npm install class-validator class-transformer

CreatePostDtoのバリデーション追加

CLIで自動生成された create-post.dto.ts を次のように編集します。

backend/src/posts/dto/create-post.dto.ts
+ import { IsString, IsNotEmpty } from 'class-validator';

export class CreatePostDto {
+ @IsString()
+ @IsNotEmpty()
  title: string;

+ @IsString()
+ @IsNotEmpty()
  content: string;
}

これにより、APIに送信されるJSONが以下のような形式であり、かつ空でないことが求められます。

{
  "title": "NestJSとPrismaで記事投稿API",
  "content": "バリデーションを通すことで、信頼できるデータ構造を担保できます。"
}

UpdatePostDto

部分更新に対応するため、UpdatePostDto ではプロパティを任意とします。
こちらはCLIで自動生成されたコードのままで問題ありません。

backend/src/posts/dto/update-post.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreatePostDto } from './create-post.dto';

export class UpdatePostDto extends PartialType(CreatePostDto) {}

バリデーションを有効にする設定

backend/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
+ import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
+ app.useGlobalPipes(new ValidationPipe());
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap().catch((err) => {
  console.error('Application failed to start', err);
});

動作確認

NestJSが自動でバリデーションエラーを検出し、エラーレスポンスを返すことを確認します。

新規記事を作成(POST /posts)

{}

空のデータをPOST /posts に送った場合は、次のようなレスポンスが返されます。

Image from Gyazo

おわりに

この記事では、NestJSとPrismaを使って、シンプルな記事投稿APIをゼロから構築しました。
Prismaを導入し、PostgreSQLとの接続を確認したうえで、モデルの定義・マイグレーション・Prisma Clientの統合・REST APIの実装・DTOによるバリデーション設計まで、一連の開発フローを段階的に進めてきました。

ここまでの構成によって、

  • 型安全で保守性の高いデータ操作
  • RESTの設計思想に沿ったエンドポイント構成
  • 不正なリクエストを防ぐバリデーションの仕組み

といった、堅牢で実践的なバックエンドAPIの土台が完成しています。

この基礎が整っていれば、今後は以下のような発展的な要素も無理なく組み込めるはずです:

  • ログインユーザーと記事を紐づける認証・認可の実装
  • ユーザー、コメント、タグなど他エンティティとのリレーション設計
  • Prismaのミドルウェアやトランザクションによる高度なDB操作
  • SwaggerによるAPIドキュメント自動生成

NestJSとPrismaをベースにしたバックエンド開発は、型安全・柔軟性・可読性のバランスが非常に良く、チーム開発にも向いています。
本記事が、自分の開発スタイルをアップデートするきっかけになれば幸いです。

次回は

ここまでで、記事投稿APIとしての基本機能はひと通り整いました。次回は、これをさらに“実用的なアプリケーション”へと高めていくための工夫に踏み込んでいきます。

  • ログの記録と整形:NestJSのLoggerを活用して、重要なイベントを把握しやすく
  • エラーハンドリングの統一:独自のExceptionFilterで例外処理の共通化
  • テスト戦略の導入:Service層のユニットテスト、Controllerを通したE2Eテストの書き方
  • 環境ごとの設定管理:テスト用 .env.test の活用と構成の分離

本番運用を見据えた「信頼されるAPIづくり」の第一歩として、アプリケーションの信頼性を高める実践的なパートとなります。

https://shinagawa-web.com/blogs/nestjs-blog-series-logging-error-testing

Xでシェア
Facebookでシェア
LinkedInでシェア

記事に関するお問い合わせ📝

記事の内容に関するご質問、ご意見などは、下記よりお気軽にお問い合わせください。
ご質問フォームへ

技術支援などお仕事に関するお問い合わせ📄

技術支援やお仕事のご依頼に関するお問い合わせは、下記よりお気軽にお問い合わせください。
お問い合わせフォームへ

関連する技術ブログ

弊社の技術支援サービス

お問い合わせ

経営と現場をつなぐ“共創型”の技術支援。
成果に直結するチーム・技術・プロセスを共に整えます。

お問い合わせ