NestJSは、Node.jsの主要なフレームワークの1つであり、最近多くの開発者からの支持と注目を集めています。この記事では、NestJS、Prisma、PostgreSQL、Swagger を使用してバックエンドの REST API を構築する方法を説明します。
目次
- はじめに
- 前提条件
- NestJSプロジェクトを生成する
- PostgreSQLインスタンスを作成する
- Prismaをセットアップする
- Swaggerをセットアップする
Article
モデルのCRUD操作を実装する- Swaggerのレスポンスタイプを更新する
- まとめと最終考察
はじめに
このチュートリアルでは、「Median」(シンプルなMediumクローン)というブログアプリケーションのバックエンド REST API を構築する方法を学びます。まず、新しいNestJSプロジェクトを作成することから始めます。次に、独自のPostgreSQLサーバーを起動し、Prismaを使用してそれに接続します。最後に、REST API を構築し、Swaggerでドキュメント化します。
使用する技術
このアプリケーションを構築するために以下のツールを使用します
- NestJSをバックエンドフレームワークとして使用
- Prismaをオブジェクトリレーショナルマッパー(ORM)として使用
- PostgreSQLをデータベースとして使用
- SwaggerをAPIドキュメントツールとして使用
- TypeScriptをプログラミング言語として使用
前提条件
前提知識
これは初心者向けのチュートリアルです。ただし、このチュートリアルでは以下の知識を前提としています
- JavaScriptまたはTypeScript(推奨)の基本的な知識
- NestJSの基本的な知識
注: NestJSに慣れていない場合は、NestJSドキュメントの概要セクションに従うことで、すぐに基本を学ぶことができます。
開発環境
このチュートリアルを進めるには、以下の準備が必要です
- ... Node.jsがインストールされていること。
- ... DockerまたはPostgreSQLがインストールされていること。
- ... Prisma VSCode拡張機能がインストールされていること。(オプション)
- ... このシリーズで提供されるコマンドを実行するために、Unixシェル(LinuxやmacOSのターミナル/シェルなど)にアクセスできること。(オプション)
注1: オプションのPrisma VSCode拡張機能は、PrismaのIntelliSenseと構文ハイライトを非常に便利にします。
注2: Unixシェルがない場合(例えば、Windowsマシンを使用している場合)でも、このチュートリアルを進めることはできますが、シェルコマンドをあなたのマシンに合わせて変更する必要があるかもしれません。
NestJSプロジェクトを生成する
まず最初に、NestJS CLIをインストールする必要があります。NestJS CLIは、NestJSプロジェクトで作業する際に非常に便利です。NestJSアプリケーションの初期化、開発、保守に役立つ組み込みユーティリティが付属しています。
NestJS CLI を使用して、空のプロジェクトを作成できます。始めるには、プロジェクトを配置したい場所で以下のコマンドを実行します
CLI は、プロジェクトのパッケージマネージャーを選択するように促します — npm を選択してください。その後、現在のディレクトリに新しい Nest JS プロジェクトが作成されます。
プロジェクトをお好みのコードエディタ(VSCodeを推奨)で開いてください。以下のファイルが表示されるはずです
作業するほとんどのコードはsrc
ディレクトリにあります。NestJS CLIはすでにいくつかのファイルを生成しています。注目すべきものとしては
src/app.module.ts
: アプリケーションのルートモジュール。src/app.controller.ts
: 単一のルート/
を持つ基本的なコントローラー。このルートはシンプルな'Hello World!'
メッセージを返します。src/main.ts
: アプリケーションのエントリーポイント。NestJSアプリケーションを起動します。
以下のコマンドを使用してプロジェクトを開始できます
このコマンドはファイルを監視し、変更があるたびにサーバーを自動的に再コンパイルおよびリロードします。サーバーが実行されていることを確認するには、URL https://:3000/
にアクセスしてください。'Hello World!'
というメッセージが表示された空のページが表示されるはずです。
注: このチュートリアルを進める間、サーバーをバックグラウンドで実行したままにしてください。
PostgreSQLインスタンスを作成する
NestJSアプリケーションのデータベースとしてPostgreSQLを使用します。このチュートリアルでは、Dockerコンテナを介してPostgreSQLをマシンにインストールし、実行する方法を示します。
注: Docker を使用したくない場合は、PostgreSQL インスタンスをネイティブにセットアップするか、Heroku でホスト型 PostgreSQL データベースを入手することができます。
まず、プロジェクトのメインフォルダーにdocker-compose.yml
ファイルを作成します
このdocker-compose.yml
ファイルは、PostgreSQLが設定されたDockerコンテナを実行するための仕様を含む設定ファイルです。ファイル内に以下の設定を作成します
この設定について理解すべき点がいくつかあります
image
オプションは、使用するDockerイメージを定義します。ここでは、バージョン13.5のpostgres
イメージを使用しています。environment
オプションは、初期化中にコンテナに渡される環境変数を指定します。ここで、コンテナが使用するユーザー名やパスワードなどの設定オプションとシークレットを定義できます。volumes
オプションは、ホストファイルシステムにデータを永続化するために使用されます。ports
オプションは、ホストマシンのポートをコンテナにマッピングします。形式は'host_port:container_port'
という慣例に従います。この場合、ホストマシンのポート5432
をpostgres
コンテナのポート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インスタンスのものに置き換えてください。
注: 前のセクションで示したように Docker を使用して PostgreSQL データベースを作成しなかった場合、接続文字列は上記のものとは異なります。PostgreSQL の接続文字列形式は、Prisma ドキュメントで入手できます。
Prismaスキーマを理解する
prisma/schema.prisma
を開くと、以下のデフォルトスキーマが表示されるはずです
このファイルは、Prisma がデータベーススキーマを定義するために使用する言語であるPrisma Schema Languageで記述されています。schema.prisma
ファイルには、主に3つのコンポーネントがあります
- データソース: データベース接続を指定します。上記の設定は、データベースのプロバイダーがPostgreSQLであり、データベース接続文字列が
DATABASE_URL
環境変数で利用できることを意味します。 - ジェネレーター: データベース用のタイプセーフなクエリビルダーであるPrisma Clientを生成することを示します。これはデータベースにクエリを送信するために使用されます。
- データモデル: データベースのモデルを定義します。各モデルは、基となるデータベースのテーブルにマッピングされます。現在、スキーマにはモデルがありません。この部分は次のセクションで詳しく説明します。
注: Prismaスキーマの詳細については、Prismaドキュメントを確認してください。
データをモデル化する
ここでアプリケーションのデータモデルを定義します。このチュートリアルでは、ブログ上の各記事を表すためにArticle
モデルのみが必要です。
prisma/prisma.schema
ファイル内に、Article
という名前の新しいモデルをスキーマに追加します
ここでは、いくつかのフィールドを持つArticle
モデルを作成しました。各フィールドには、名前(id
、title
など)、型(Int
、String
など)、およびその他のオプション属性(@id
、@unique
など)があります。フィールドは、フィールド型の後に?
を追加することでオプションにできます。
id
フィールドには、@id
という特別な属性があります。この属性は、このフィールドがモデルの主キーであることを示します。@default(autoincrement())
属性は、このフィールドが自動的にインクリメントされ、新しく作成されたすべてのレコードに割り当てられるべきであることを示します。
published
フィールドは、記事が公開されているか、下書きモードであるかを示すフラグです。@default(false)
属性は、このフィールドがデフォルトでfalse
に設定されるべきであることを示します。
2つのDateTime
フィールド、createdAt
とupdatedAt
は、記事が作成された日時と最終更新日時を追跡します。@updatedAt
属性は、記事が変更されるたびにフィールドを現在のタイムスタンプで自動的に更新します。
データベースをマイグレーションする
Prismaスキーマが定義されたので、データベースに実際のテーブルを作成するためにマイグレーションを実行します。最初のマイグレーションを生成して実行するには、ターミナルで以下のコマンドを実行します
このコマンドは3つのことを行います
- マイグレーションの保存: Prisma Migrateはスキーマのスナップショットを取得し、マイグレーションを実行するために必要なSQLコマンドを特定します。Prismaは、SQLコマンドを含むマイグレーションファイルを新しく作成された
prisma/migrations
フォルダに保存します。 - マイグレーションの実行: Prisma Migrateは、マイグレーションファイル内のSQLを実行して、データベースに基となるテーブルを作成します。
- 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によって自動的に生成され、実行されました。
データベースに初期データを投入する
現在、データベースは空です。そこで、ダミーデータでデータベースを投入するシードスクリプトを作成します。
まず、prisma/seed.ts
という名前のシードファイルを作成します。このファイルには、データベースをシードするために必要なダミーデータとクエリが含まれます。
次に、シードファイル内に以下のコードを追加します
このスクリプト内では、まずPrisma Clientを初期化します。次に、prisma.upsert()
関数を使用して2つの記事を作成します。upsert
関数は、where
条件に一致する記事がない場合にのみ、新しい記事を作成します。create
クエリの代わりにupsert
クエリを使用しているのは、upsert
が誤って同じレコードを二度挿入しようとすることに関連するエラーを取り除くためです。
シーディングコマンドを実行する際に、Prismaにどのスクリプトを実行するかを伝える必要があります。これを行うには、prisma.seed
キーをpackage.json
ファイルの末尾に追加します
seed
コマンドは、以前に定義したprisma/seed.ts
スクリプトを実行します。このコマンドは、ts-node
がすでにpackage.json
の開発依存関係としてインストールされているため、自動的に機能するはずです。
以下のコマンドでシーディングを実行します
以下の出力が表示されるはずです
注: シーディングの詳細については、Prisma ドキュメントを参照してください。
Prismaサービスを作成する
NestJSアプリケーション内では、Prisma Client APIをアプリケーションから抽象化するのが良い習慣です。これを行うには、Prisma Clientを含む新しいサービスを作成します。PrismaService
というこのサービスは、PrismaClient
インスタンスをインスタンス化し、データベースに接続する役割を担います。
Nest CLIを使用すると、CLIから直接モジュールとサービスを簡単に生成できます。ターミナルで以下のコマンドを実行します
注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
配列に追加します
これで、PrismaModule
をインポートするすべてのモジュールはPrismaService
にアクセスでき、それを自身のコンポーネント/サービスに注入できます。これはNestJSアプリケーションの一般的なパターンです。
これでPrismaのセットアップは完了です!REST APIの構築に取り掛かることができます。
Swaggerをセットアップする
Swaggerは、OpenAPI仕様を使用してAPIを文書化するためのツールです。NestにはSwagger用の専用モジュールがあり、すぐに使用します。
必要な依存関係をインストールすることから始めます
次に、main.ts
を開き、SwaggerModule
クラスを使用してSwaggerを初期化します
アプリケーションが実行中に、ブラウザを開いてhttps://:3000/api
にアクセスしてください。Swagger UIが表示されるはずです。
Article
モデルのCRUD操作を実装する
このセクションでは、Article
モデルの作成、読み取り、更新、削除(CRUD)操作、およびそれに伴うビジネスロジックを実装します。
RESTリソースを生成する
REST APIを実装する前に、Article
モデルのRESTリソースを生成する必要があります。これはNest CLIを使用してすばやく実行できます。ターミナルで以下のコマンドを実行します
いくつかのCLIプロンプトが表示されます。質問に適切に答えてください
このリソースにどのような名前を使用しますか(複数形、例: "users")?
articlesどのトランスポート層を使用しますか?
REST APICRUDエントリーポイントを生成しますか?
はい
これで、RESTエンドポイントのすべてのボイラープレートを含む新しいsrc/articles
ディレクトリが見つかるはずです。src/articles/articles.controller.ts
ファイル内には、さまざまなルート(ルートハンドラーとも呼ばれます)の定義が表示されます。各リクエストを処理するためのビジネスロジックは、src/articles/articles.service.ts
ファイルにカプセル化されています。現在、このファイルにはダミーの実装が含まれています。
SwaggerのAPIページをもう一度開くと、次のような表示になるはずです
SwaggerModule
は、ルートハンドラー上のすべての@Body()
、@Query()
、および@Param()
デコレーターを検索して、このAPIページを生成します。
PrismaClient
をArticles
モジュールに追加する
Articles
モジュール内でPrismaClient
にアクセスするには、PrismaModule
をインポートとして追加する必要があります。ArticlesModule
に以下のimports
を追加します
これで、ArticlesService
内にPrismaService
を注入し、それを使用してデータベースにアクセスできます。これを行うには、articles.service.ts
に以下のようにコンストラクタを追加します
GET /articles
エンドポイントを定義する
このエンドポイントのコントローラーはfindAll
と呼ばれます。このエンドポイントは、データベース内のすべての公開済み記事を返します。findAll
コントローラーは次のようになります
ArticlesService.findAll()
を更新して、データベース内のすべての公開済み記事の配列を返すようにする必要があります
findMany
クエリは、where
条件に一致するすべてのarticle
レコードを返します。
エンドポイントをテストするには、https://:3000/api
にアクセスし、GET/articlesドロップダウンメニューをクリックします。Try it outを押してから、Executeを押して結果を確認します。
注: すべてのリクエストは、ブラウザで直接実行するか、RESTクライアント(Postmanなど)を介して実行することもできます。Swaggerは、ターミナルでHTTPリクエストを実行したい場合に備えて、各リクエストのcurlコマンドも生成します。
GET /articles/drafts
エンドポイントを定義する
すべての非公開記事を取得するための新しいルートを定義します。NestJSはこのエンドポイントのコントローラールートハンドラーを自動的に生成しなかったため、自分で記述する必要があります。
エディタには、articlesService.findDrafts()
という関数が存在しないというエラーが表示されるはずです。これを修正するには、ArticlesService
にfindDrafts
メソッドを実装します
GET /articles/drafts
エンドポイントは、SwaggerのAPIページで利用できるようになります。
注: 各エンドポイントの実装が完了したら、SwaggerのAPIページでテストすることをお勧めします。
GET /articles/:id
エンドポイントを定義する
このエンドポイントのコントローラールートハンドラーはfindOne
と呼ばれます。それは次のようになります
このルートは動的なid
パラメータを受け入れ、それはfindOne
コントローラールートハンドラーに渡されます。Article
モデルには整数型のid
フィールドがあるため、id
パラメータは+
演算子を使用して数値にキャストする必要があります。
次に、ArticlesService
のfindOne
メソッドを更新して、指定されたIDを持つ記事を返すようにします
もう一度、https://: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内に定義されています。
次に、ArticlesService
のcreate
メソッドを更新して、データベースに新しい記事を作成します
PATCH /articles/:id
エンドポイントを定義する
このエンドポイントは、既存の記事を更新するためのものです。このエンドポイントのルートハンドラーはupdate
と呼ばれます。それは次のようになります
updateArticleDto
の定義は、CreateArticleDto
のPartialType
として定義されています。したがって、CreateArticleDto
のすべてのプロパティを持つことができます。
以前と同様に、この操作に対応するサービスメソッドを更新する必要があります
article.update
操作は、指定されたid
を持つArticle
レコードを検索し、updateArticleDto
のデータで更新しようとします。
データベースにそのようなArticle
レコードが見つからない場合、Prismaはエラーを返します。そのような場合、APIはユーザーフレンドリーなエラーメッセージを返しません。NestJSでのエラーハンドリングについては、今後のチュートリアルで学びます。
DELETE /articles/:id
エンドポイントを定義する
このエンドポイントは、既存の記事を削除するためのものです。このエンドポイントのルートハンドラーはremove
と呼ばれます。それは次のようになります
以前と同様に、ArticlesService
に移動し、対応するメソッドを更新します
これでarticles
エンドポイントの最後の操作でした。おめでとうございます、APIの準備はほぼ完了です!🎉
Swaggerでエンドポイントをグループ化する
Swaggerでarticles
エンドポイントをすべてグループ化するために、ArticlesController
クラスに@ApiTags
デコレータを追加します
APIページでは、articles
エンドポイントがグループ化されるようになりました。
Swaggerのレスポンスタイプを更新する
Swaggerの各エンドポイントの下にあるResponsesタブを見ると、Descriptionが空であることがわかります。これは、Swaggerがどのエンドポイントのレスポンスタイプも知らないためです。これをいくつかのデコレータを使用して修正します。
まず、Swaggerが返されるentity
オブジェクトの形状を識別するために使用できるエンティティを定義する必要があります。これを行うには、articles.entity.ts
ファイル内のArticleEntity
クラスを以下のように更新します
これは、Prisma Clientによって生成されたArticle
型の実装であり、各プロパティに@ApiProperty
デコレータが追加されています。
次に、コントローラーのルートハンドラーに正しいレスポンスタイプをアノテーションします。NestJSには、この目的のためのデコレータのセットがあります。
GET
、PATCH
、DELETE
エンドポイントには@ApiOkResponse
を、POST
エンドポイントには@ApiCreatedResponse
を追加しました。type
プロパティは、戻り値を指定するために使用されます。NestJSが提供するすべてのレスポンスデコレータは、NestJSドキュメントで見つけることができます。
これで、APIページ上のすべてのエンドポイントについて、Swaggerがレスポンスタイプを適切に定義するはずです。
まとめと最終考察
おめでとうございます!NestJSを使用して基本的なREST APIを構築しました。このチュートリアルを通して、あなたは以下を学びました
- NestJSでREST APIを構築
- NestJSプロジェクトにPrismaをスムーズに統合
- SwaggerとOpenAPIを使用してREST APIを文書化
このチュートリアルから得られる主な教訓の1つは、NestJSとPrismaを使用してREST APIを構築することがいかに簡単であるかということです。これは、構造が良好で、タイプセーフで、保守が容易なバックエンドアプリケーションを迅速に構築するための、非常に生産性の高いスタックです。
このプロジェクトのソースコードはGitHubで公開されています。問題にお気づきの場合は、遠慮なくリポジトリでissueを立てるか、プルリクエストを送信してください。また、Twitterで直接連絡することもできます。
次の投稿をお見逃しなく!
Prismaニュースレターに登録する