2022年6月3日

NestJSとPrismaを使用したREST APIの構築

読了時間20分

NestJSは著名なNode.jsフレームワークの一つであり、最近多くの開発者からの支持と注目を集めています。この記事では、NestJS、Prisma、PostgreSQL、Swaggerを使用してバックエンドREST APIを構築する方法を学びます。

Building a REST API with NestJS and Prisma

目次

はじめに

このチュートリアルでは、「Median」(シンプルなMediumクローン)と呼ばれるブログアプリケーションのバックエンドREST APIを構築する方法を学びます。まず、新しいNestJSプロジェクトを作成することから始めます。次に、独自のPostgreSQLサーバーを起動し、Prismaを使用して接続します。最後に、REST APIを構築し、Swaggerでドキュメント化します。

The final application

使用する技術

このアプリケーションを構築するために、次のツールを使用します。

前提条件

前提知識

これは初心者向けのチュートリアルです。ただし、このチュートリアルは以下を前提としています。

  • JavaScriptまたはTypeScriptの基本的な知識(推奨)
  • NestJSの基本的な知識

:NestJSに慣れていない場合は、NestJSドキュメントの概要セクションに従って、基本をすぐに学ぶことができます。

開発環境

このチュートリアルに従うには、以下が必要です。

  • ... Node.jsがインストールされていること。
  • ... DockerまたはPostgreSQLがインストールされていること。
  • ... Prisma VSCode Extensionがインストールされていること(オプション)。
  • ... このシリーズで提供されるコマンドを実行するために、Unixシェル(LinuxおよびmacOSのターミナル/シェルなど)にアクセスできること(オプション)。

注1:オプションのPrisma VSCode拡張機能は、Prismaに非常に優れたIntelliSenseと構文ハイライトを追加します。

注2:Unixシェルがない場合(たとえば、Windowsマシンを使用している場合)、それでもチュートリアルに従うことはできますが、シェルコマンドをマシンに合わせて変更する必要がある場合があります。

NestJSプロジェクトの生成

最初に必要なのは、NestJS CLIをインストールすることです。NestJS CLIは、NestJSプロジェクトを操作する際に非常に役立ちます。NestJSアプリケーションの初期化、開発、および保守に役立つ組み込みユーティリティが付属しています。

NestJS CLIを使用して、空のプロジェクトを作成できます。開始するには、プロジェクトを配置する場所で次のコマンドを実行します。

CLIは、プロジェクトのパッケージマネージャーを選択するように求めます—npmを選択してください。その後、現在のディレクトリに新しいNestJSプロジェクトが作成されます。

プロジェクトをお好みのコードエディター(VSCodeを推奨)で開きます。次のファイルが表示されるはずです。

作業するコードのほとんどは、srcディレクトリに配置されます。NestJS CLIは、いくつかのファイルをすでに作成しています。注目すべきファイルの一部は次のとおりです。

  • src/app.module.ts:アプリケーションのルートモジュール。
  • src/app.controller.ts:単一のルート/を持つ基本的なコントローラー。このルートは、シンプルな'Hello World!'メッセージを返します。
  • src/main.ts:アプリケーションのエントリーポイント。NestJSアプリケーションを起動します。

次のコマンドを使用してプロジェクトを開始できます。

このコマンドはファイルを監視し、変更を加えるたびにサーバーを自動的に再コンパイルおよびリロードします。サーバーが実行されていることを確認するには、URL http://localhost:3000/ にアクセスしてください。「Hello World!」というメッセージが表示された空白のページが表示されるはずです。

:このチュートリアルを進める間、サーバーをバックグラウンドで実行し続ける必要があります。

PostgreSQLインスタンスの作成

NestJSアプリケーションのデータベースとしてPostgreSQLを使用します。このチュートリアルでは、Dockerコンテナを介してPostgreSQLをマシンにインストールして実行する方法を示します。

:Dockerを使用したくない場合は、PostgreSQLインスタンスをネイティブに設定するか、HerokuでホストされているPostgreSQLデータベースを取得できます。

まず、プロジェクトのメインフォルダーに docker-compose.yml ファイルを作成します。

この docker-compose.yml ファイルは、PostgreSQLの設定が内部にあるDockerコンテナを実行するための仕様を含む構成ファイルです。ファイル内に次の構成を作成します。

この構成について理解すべき点がいくつかあります。

  • imageオプションは、使用するDockerイメージを定義します。ここでは、postgresイメージバージョン13.5を使用しています。
  • environmentオプションは、初期化中にコンテナに渡される環境変数を指定します。コンテナが使用する構成オプションとシークレット(ユーザー名やパスワードなど)をここで定義できます。
  • volumesオプションは、ホストファイルシステムにデータを永続化するために使用されます。
  • portsオプションは、ホストマシンからコンテナへのポートをマッピングします。形式は、「host_port:container_port」の規則に従います。この場合、ホストマシンのポート5432postgresコンテナのポート5432にマッピングしています。5432は、PostgreSQLが慣例的に使用するポートです。

マシンのポート5432で何も実行されていないことを確認してください。postgresコンテナを起動するには、新しいターミナルウィンドウを開き、プロジェクトのメインフォルダーで次のコマンドを実行します。

すべてが正しく機能した場合、新しいターミナルウィンドウには、データベースシステムが接続を受け入れる準備ができていることを示すログが表示されるはずです。ターミナルウィンドウ内に次のようなログが表示されるはずです。

おめでとうございます🎉。これで、独自のPostgreSQLデータベースを自由に使えるようになりました。

:ターミナルウィンドウを閉じると、コンテナも停止します。これを避けるには、コマンドの最後に -d オプションを追加します(docker-compose up -dなど)。これにより、コンテナがバックグラウンドで無期限に実行されます。

Prismaの設定

データベースの準備ができたので、Prismaを設定する時が来ました。

Prismaの初期化

開始するには、まず開発依存関係としてPrisma CLIをインストールします。Prisma CLIを使用すると、さまざまなコマンドを実行してプロジェクトを操作できます。

プロジェクト内でPrismaを初期化するには、次を実行します。

これにより、schema.prismaファイルを含む新しいprismaディレクトリが作成されます。これは、データベーススキーマを含むメインの構成ファイルです。このコマンドは、プロジェクト内に.envファイルも作成します。

環境変数の設定

.envファイル内に、ダミーの接続文字列を持つDATABASE_URL環境変数があるはずです。この接続文字列をPostgreSQLインスタンスの接続文字列に置き換えます。

:PostgreSQLデータベースを作成するためにdocker(前のセクションで示したように)を使用しなかった場合、接続文字列は上に示したものとは異なります。PostgreSQLの接続文字列形式は、Prismaドキュメントで入手できます。

Prismaスキーマの理解

prisma/schema.prismaを開くと、次のデフォルトスキーマが表示されるはずです。

このファイルはPrismaスキーマ言語で記述されており、これはPrismaがデータベーススキーマを定義するために使用する言語です。schema.prismaファイルには、主に3つのコンポーネントがあります。

  • データソース:データベース接続を指定します。上記の構成は、データベースプロバイダーがPostgreSQLであり、データベース接続文字列がDATABASE_URL環境変数で利用可能であることを意味します。
  • ジェネレーター:データベース用のタイプセーフクエリビルダーであるPrisma Clientを生成することを示します。これは、データベースにクエリを送信するために使用されます。
  • データモデル:データベースモデルを定義します。各モデルは、基盤となるデータベースのテーブルにマッピングされます。現在、スキーマにはモデルはありません。この部分については、次のセクションで詳しく説明します。

:Prismaスキーマの詳細については、Prismaドキュメントを確認してください。

データモデリング

ここで、アプリケーションのデータモデルを定義します。このチュートリアルでは、ブログの各記事を表すArticleモデルのみが必要です。

prisma/prisma.schemaファイル内で、Articleという名前の新しいモデルをスキーマに追加します。

ここでは、いくつかのフィールドを持つArticleモデルを作成しました。各フィールドには、名前(idtitleなど)、型(IntStringなど)、およびその他のオプション属性(@id@uniqueなど)があります。フィールドをオプションにするには、フィールド型の後に?を追加します。

idフィールドには、@idと呼ばれる特別な属性があります。この属性は、このフィールドがモデルの主キーであることを示します。@default(autoincrement())属性は、このフィールドが自動的にインクリメントされ、新しく作成されたレコードに割り当てられる必要があることを示します。

publishedフィールドは、記事が公開されているかドラフトモードであるかを示すフラグです。@default(false)属性は、このフィールドがデフォルトでfalseに設定される必要があることを示します。

2つのDateTimeフィールド、createdAtupdatedAtは、記事がいつ作成されたか、最後にいつ更新されたかを追跡します。@updatedAt属性は、記事が変更されるたびに、現在のタイムスタンプでフィールドを自動的に更新します。

データベースの移行

Prismaスキーマが定義されたので、移行を実行してデータベースに実際のテーブルを作成します。最初の移行を生成して実行するには、ターミナルで次のコマンドを実行します。

このコマンドは3つのことを行います。

  1. 移行の保存:Prisma Migrateは、スキーマのスナップショットを取得し、移行を実行するために必要なSQLコマンドを把握します。Prismaは、SQLコマンドを含む移行ファイルを、新しく作成されたprisma/migrationsフォルダーに保存します。
  2. 移行の実行:Prisma Migrateは、移行ファイル内のSQLを実行して、データベースに基盤となるテーブルを作成します。
  3. Prisma Clientの生成:Prismaは、最新のスキーマに基づいてPrisma Clientを生成します。Clientライブラリがインストールされていないため、CLIもインストールします。package.jsonファイルのdependencies内に@prisma/clientパッケージが表示されるはずです。Prisma Clientは、Prismaスキーマから自動生成されたTypeScriptクエリビルダーです。Prismaスキーマに合わせて調整されており、データベースにクエリを送信するために使用されます。

:Prisma Migrateの詳細については、Prismaドキュメントを参照してください。

正常に完了すると、次のようなメッセージが表示されるはずです。

生成された移行ファイルを確認して、Prisma Migrateがバックグラウンドで何をしているのかを理解してください。

:移行ファイルの名前はわずかに異なります。

これは、PostgreSQLデータベース内にArticleテーブルを作成するために必要なSQLです。Prismaスキーマに基づいて、Prismaによって自動的に生成および実行されました。

データベースのシード

現在、データベースは空です。そのため、ダミーデータでデータベースをpopulateするシードスクリプトを作成します。

まず、prisma/seed.tsという名前のシードファイルを作成します。このファイルには、データベースをシードするために必要なダミーデータとクエリが含まれます。

次に、シードファイル内に次のコードを追加します。

このスクリプト内では、最初にPrisma Clientを初期化します。次に、prisma.upsert()関数を使用して2つの記事を作成します。upsert関数は、where条件に一致する記事がない場合にのみ、新しい記事を作成します。createクエリの代わりにupsertクエリを使用しているのは、upsertが同じレコードを誤って2回挿入しようとしたことに関連するエラーを削除するためです。

シーディングコマンドを実行するときに実行するスクリプトをPrismaに指示する必要があります。package.jsonファイルの最後にprisma.seedキーを追加することで、これを行うことができます。

seedコマンドは、以前に定義したprisma/seed.tsスクリプトを実行します。ts-nodepackage.jsonに開発依存関係としてすでにインストールされているため、このコマンドは自動的に機能するはずです。

次のコマンドでシーディングを実行します。

次の出力が表示されるはずです。

:シーディングの詳細については、Prismaドキュメントを参照してください。

Prismaサービスの作成

NestJSアプリケーション内では、Prisma Client APIをアプリケーションから抽象化することをお勧めします。これを行うには、Prisma Clientを含む新しいサービスを作成します。PrismaServiceと呼ばれるこのサービスは、PrismaClientインスタンスをインスタンス化し、データベースに接続する役割を担います。

Nest CLIを使用すると、モジュールとサービスをCLIから直接簡単に生成できます。ターミナルで次のコマンドを実行します。

注1:必要に応じて、サービスモジュールの概要については、NestJSドキュメントを参照してください。

注2:場合によっては、サーバーがすでに実行されている状態でnest generateコマンドを実行すると、NestJSが「Error: Cannot find module './app.controller'」という例外をスローする可能性があります。このエラーが発生した場合は、ターミナルから次のコマンドを実行します:rm -rf dist を実行してサーバーを再起動します。

これにより、prisma.module.tsファイルとprisma.service.tsファイルを含む新しいサブディレクトリ./src/prismaが生成されるはずです。サービスファイルには、次のコードが含まれているはずです。

Prismaモジュールは、PrismaServiceシングルトンインスタンスを作成し、アプリケーション全体でサービスを共有できるようにする役割を担います。これを行うには、prisma.module.tsファイルのexports配列にPrismaServiceを追加します。

これで、PrismaModuleをインポートするモジュールは、PrismaServiceにアクセスできるようになり、独自のコンポーネント/サービスに注入できます。これは、NestJSアプリケーションの一般的なパターンです。

これで、Prismaの設定は完了です。REST APIの構築に取り掛かることができます。

Swaggerの設定

Swaggerは、OpenAPI仕様を使用してAPIをドキュメント化するツールです。NestにはSwagger専用のモジュールがあり、まもなく使用します。

まず、必要な依存関係をインストールします。

次に、main.tsを開き、SwaggerModuleクラスを使用してSwaggerを初期化します。

アプリケーションが実行されている間に、ブラウザを開いてhttp://localhost:3000/apiに移動します。Swagger UIが表示されるはずです。

Swagger User Interface

ArticleモデルのCRUD操作の実装

このセクションでは、Articleモデルの作成、読み取り、更新、および削除(CRUD)操作と、付随するビジネスロジックを実装します。

RESTリソースの生成

REST APIを実装する前に、ArticleモデルのRESTリソースを生成する必要があります。これは、Nest CLIを使用してすばやく行うことができます。ターミナルで次のコマンドを実行します。

CLIプロンプトがいくつか表示されます。質問に適切に答えてください。

  1. What name would you like to use for this resource (plural, e.g., "users")? articles
  2. What transport layer do you use? REST API
  3. Would you like to generate CRUD entry points? Yes

これで、RESTエンドポイントのすべてのボイラープレートを含む新しいsrc/articlesディレクトリが見つかるはずです。src/articles/articles.controller.tsファイル内には、さまざまなルート(ルートハンドラーとも呼ばれます)の定義が表示されます。各リクエストを処理するためのビジネスロジックは、src/articles/articles.service.tsファイルにカプセル化されています。現在、このファイルにはダミーの実装が含まれています。

Swagger APIページを再度開くと、次のようなものが表示されるはずです。

Auto-generated "articles" endpoints

SwaggerModuleは、ルートハンドラーのすべての@Body()@Query()、および@Param()デコレーターを検索して、このAPIページを生成します。

ArticlesモジュールへのPrismaClientの追加

Articlesモジュール内でPrismaClientにアクセスするには、PrismaModuleをインポートとして追加する必要があります。次のimportsArticlesModuleに追加します。

これで、PrismaServiceArticlesService内に注入し、それを使用してデータベースにアクセスできます。これを行うには、articles.service.tsに次のようなコンストラクターを追加します。

GET /articles エンドポイントの定義

このエンドポイントのコントローラーはfindAllと呼ばれます。このエンドポイントは、データベース内の公開されているすべての記事を返します。findAllコントローラーは次のようになります。

データベース内のすべての公開済み記事の配列を返すようにArticlesService.findAll()を更新する必要があります。

findManyクエリは、where条件に一致するすべてのarticleレコードを返します。

エンドポイントをテストするには、http://localhost:3000/apiに移動し、GET/articlesドロップダウンメニューをクリックします。「Try it out」を押してから「Execute」を押して結果を確認します。

:すべてのリクエストをブラウザで直接実行するか、RESTクライアント(Postmanなど)を介して実行することもできます。Swaggerは、ターミナルでHTTPリクエストを実行する場合に備えて、各リクエストのcurlコマンドも生成します。

GET /articles/drafts エンドポイントの定義

未公開の記事をすべて取得するための新しいルートを定義します。NestJSはこのエンドポイントのコントローラールートハンドラーを自動的に生成しなかったため、自分で記述する必要があります。

エディターは、articlesService.findDrafts() という関数が存在しないというエラーを表示するはずです。これを修正するには、ArticlesServicefindDrafts メソッドを実装してください。

GET /articles/drafts エンドポイントが Swagger API ページで利用可能になります。

注意: 各エンドポイントの実装が完了したら、Swagger API ページでテストすることを推奨します。

GET /articles/:id エンドポイントを定義する

このエンドポイントのコントローラールートハンドラーは findOne と呼ばれます。以下のようになります。

このルートは動的な id パラメーターを受け入れ、それは findOne コントローラールートハンドラーに渡されます。Article モデルは integer 型の id フィールドを持っているため、id パラメーターは + 演算子を使用して数値にキャストする必要があります。

次に、ArticlesServicefindOne メソッドを更新して、指定された ID の記事を返すようにします。

もう一度、http://localhost:3000/api にアクセスしてエンドポイントをテストしてください。「GET /articles/{id}」ドロップダウンメニューをクリックします。「Try it out」を押し、「id」パラメーターに有効な値を入力し、「Execute」を押して結果を確認します。

POST /articles エンドポイントを定義する

これは新しい記事を作成するためのエンドポイントです。このエンドポイントのコントローラールートハンドラーは create と呼ばれます。以下のようになります。

リクエストボディで CreateArticleDto 型の引数を予期していることに注意してください。DTO(Data Transfer Object)は、ネットワーク上でデータがどのように送信されるかを定義するオブジェクトです。現在、CreateArticleDto は空のクラスです。リクエストボディの形状を定義するために、プロパティを追加します。

@ApiProperty デコレーターは、クラスプロパティを SwaggerModule に表示するために必要です。詳細については、NestJS ドキュメントを参照してください。

CreateArticleDto は Swagger API ページの Schemas の下に定義されているはずです。UpdateArticleDto の形状は CreateArticleDto の定義から自動的に推測されます。したがって、UpdateArticleDto も Swagger 内で定義されています。

次に、データベースに新しい記事を作成するために、ArticlesServicecreate メソッドを更新します。

PATCH /articles/:id エンドポイントを定義する

これは既存の記事を更新するためのエンドポイントです。このエンドポイントのルートハンドラーは update と呼ばれます。以下のようになります。

updateArticleDto の定義は、CreateArticleDtoPartialType として定義されています。したがって、CreateArticleDto のすべてのプロパティを持つことができます。

先ほどと同様に、この操作に対応するサービスメソッドを更新する必要があります。

article.update 操作は、指定された id を持つ Article レコードを検索し、updateArticleDto のデータで更新しようとします。

データベース内にそのような Article レコードが見つからない場合、Prisma はエラーを返します。そのような場合、API はユーザーフレンドリーなエラーメッセージを返しません。NestJS でのエラー処理については、今後のチュートリアルで学習します。

DELETE /articles/:id エンドポイントを定義する

これは既存の記事を削除するためのエンドポイントです。このエンドポイントのルートハンドラーは remove と呼ばれます。以下のようになります。

先ほどと同様に、ArticlesService に移動し、対応するメソッドを更新します。

これで articles エンドポイントの最後の操作です。おめでとうございます!API はほぼ完成です!🎉

Swagger でエンドポイントをグループ化する

すべての articles エンドポイントを Swagger でグループ化するために、ArticlesController クラスに @ApiTags デコレーターを追加します。

API ページには、articles エンドポイントがグループ化されて表示されるようになりました。

Swagger のレスポンス型を更新する

Swagger の各エンドポイントの下にある Responses タブを見ると、Description が空であることがわかります。これは、Swagger がどのエンドポイントのレスポンス型も認識していないためです。これをいくつかのデコレーターを使用して修正します。

まず、Swagger が返される entity オブジェクトの形状を識別するために使用できるエンティティを定義する必要があります。これを行うには、articles.entity.ts ファイルの ArticleEntity クラスを次のように更新します。

これは、Prisma Client によって生成された Article 型の実装であり、各プロパティに @ApiProperty デコレーターが追加されています。

次に、コントローラールートハンドラーに正しいレスポンス型を注釈する必要があります。NestJS にはこの目的のためのデコレーターセットがあります。

GETPATCHDELETE エンドポイントには @ApiOkResponse を、POST エンドポイントには @ApiCreatedResponse を追加しました。type プロパティは、戻り値の型を指定するために使用されます。NestJS が提供するすべてのレスポンスデコレーターは、NestJS ドキュメントにあります。

これで、Swagger は API ページのすべてのエンドポイントのレスポンス型を適切に定義するはずです。

まとめと最後の注意

おめでとうございます!NestJS を使用して基本的な REST API を構築しました。このチュートリアルを通して、あなたは

  • NestJS で REST API を構築しました
  • NestJS プロジェクトに Prisma をスムーズに統合しました
  • Swagger と OpenAPI を使用して REST API をドキュメント化しました

このチュートリアルの主なポイントの 1 つは、NestJS と Prisma で REST API を構築することがいかに簡単かということです。これは、構造化され、型安全で、保守性の高いバックエンドアプリケーションを迅速に構築するための非常に生産的なスタックです。

このプロジェクトのソースコードは GitHub で見つけることができます。問題に気づいた場合は、遠慮なくリポジトリに issue を作成するか、PR を送信してください。また、Twitter で直接私に連絡することもできます。

次の記事をお見逃しなく!

Prisma ニュースレターに登録する