Prismaを使用してGraphQLAPIを構築し、DigitalOceanのアプリプラットフォームにデプロイする方法
著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
GraphQL は、スキーマ定義言語とクエリ言語で構成されるAPIのクエリ言語であり、APIコンシューマーは、柔軟なクエリをサポートするために必要なデータのみをフェッチできます。 GraphQLを使用すると、開発者は、iOS、Android、アプリのWebバリアントなど、複数のクライアントのさまざまなニーズに対応しながらAPIを進化させることができます。 さらに、GraphQLスキーマは、APIにある程度の型安全性を追加すると同時に、APIのドキュメントの形式としても機能します。
Prisma は、オープンソースのデータベースツールキットです。 これは、3つの主要なツールで構成されています。
- Prismaクライアント :Node.jsおよびTypeScript用の自動生成されたタイプセーフなクエリビルダー。
- Prisma Migrate :宣言型データモデリングおよび移行システム。
- Prisma Studio :データベース内のデータを表示および編集するためのGUI。
Prismaは、複雑なデータベースワークフロー(スキーマの移行や複雑なSQLクエリの作成など)に時間を費やすのではなく、付加価値機能の実装に集中したいアプリケーション開発者のために、データベースの操作を容易にします。
このチュートリアルでは、GraphQLとPrismaを組み合わせて使用します。これは、それらの責任が互いに補完し合うためです。 GraphQLは、フロントエンドやモバイルアプリなどのクライアントで使用するためのデータへの柔軟なインターフェイスを提供します。GraphQLは特定のデータベースに関連付けられていません。 これは、データが保存されるデータベースとの相互作用を処理するためにPrismaが登場する場所です。
DigitalOceanのAppPlatformは、インフラストラクチャを気にすることなく、クラウドにアプリケーションをデプロイしてデータベースをプロビジョニングするシームレスな方法を提供します。 これにより、クラウドでアプリケーションを実行する際の運用上のオーバーヘッドが削減されます。 特に、毎日のバックアップと自動フェイルオーバーを備えたマネージドPostgreSQLデータベースを作成する機能を備えています。 App Platformには、デプロイメントの合理化をサポートするネイティブNode.jsがあります。
Node.jsを使用して、JavaScriptでブログアプリケーション用のGraphQLAPIを構築します。 最初にApolloServerを使用して、メモリ内のデータ構造に裏打ちされたGraphQLAPIを構築します。 次に、APIをDigitalOceanAppPlatformにデプロイします。 最後に、Prismaを使用してメモリ内ストレージを置き換え、データをPostgreSQLデータベースに永続化して、アプリケーションを再度デプロイします。
チュートリアルの最後に、Node.js GraphQL APIをDigitalOceanにデプロイします。これは、HTTP経由で送信されたGraphQLリクエストを処理し、PostgreSQLデータベースに対してCRUD操作を実行します。
このプロジェクトのコードは、DigitalOceanCommunityリポジトリにあります。
前提条件
このガイドを開始する前に、次のものが必要です。
- GitHubアカウント。
- DigitalOceanアカウント。
- Gitがコンピューターにインストールされています。 チュートリアルオープンソースへの貢献:Git入門に従って、コンピューターにGitをインストールしてセットアップすることができます。
- Node.jsv10以降がコンピューターにインストールされています。 チュートリアルNode.jsをインストールしてローカル開発環境を作成する方法に従って、コンピューターにNode.jsをインストールしてセットアップできます。
- Docker がコンピューターにインストールされています(PostgreSQLデータベースをローカルで実行するため)。
JavaScript 、 Node.js 、GraphQL、およびPostgreSQLの基本的な知識は役に立ちますが、このチュートリアルでは厳密には必要ありません。
ステップ1—Node.jsプロジェクトを作成する
このステップでは、npmを使用してNode.jsプロジェクトをセットアップし、依存関係apollo-server
およびgraphql
をインストールします。
このプロジェクトは、このチュートリアル全体で構築およびデプロイするGraphQLAPIの基盤になります。
まず、プロジェクトの新しいディレクトリを作成します。
mkdir prisma-graphql
次に、ディレクトリに移動して、空のnpmプロジェクトを初期化します。
cd prisma-graphql npm init --yes
このコマンドは、npmプロジェクトの構成ファイルとして使用される最小限のpackage.json
ファイルを作成します。
次の出力が表示されます。
OutputWrote to /Users/yourusaername/workspace/prisma-graphql/package.json: { "name": "prisma-graphql", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
これで、プロジェクトでTypeScriptを構成する準備が整いました。
次のコマンドを実行して、必要な依存関係をインストールします。
npm install apollo-server graphql --save
これにより、プロジェクトに依存関係として2つのパッケージがインストールされます。
- apollo-server :GraphQLリクエストの解決方法とデータのフェッチ方法を定義するために使用するHTTPライブラリ。
- graphql :GraphQLスキーマを構築するために使用するライブラリです。
プロジェクトを作成し、依存関係をインストールしました。 次のステップでは、GraphQLスキーマを定義します。
ステップ2—GraphQLスキーマとリゾルバーを定義する
このステップでは、GraphQLスキーマと対応するリゾルバーを定義します。 スキーマは、APIが処理できる操作を定義します。 リゾルバーは、メモリ内のデータ構造を使用してこれらのリクエストを処理するためのロジックを定義します。これは、次のステップでデータベースクエリに置き換えます。
まず、ソースファイルを含むsrc
という名前の新しいディレクトリを作成します。
mkdir src
次に、次のコマンドを実行して、スキーマのファイルを作成します。
nano src/schema.js
次に、次のコードをファイルに追加します。
プリズム-graphql/src / schema.js
const { gql } = require('apollo-server') const typeDefs = gql` type Post { content: String id: ID! published: Boolean! title: String! } type Query { feed: [Post!]! post(id: ID!): Post } type Mutation { createDraft(content: String, title: String!): Post! publish(id: ID!): Post } `
ここでは、gql
タグ付きテンプレートを使用してGraphQLスキーマを定義します。 スキーマは、APIに対して実行できるクエリの形状を一緒に定義するタイプ定義(したがってtypeDefs
)のコレクションです。 これにより、GraphQLスキーマ文字列がApolloが期待する形式に変換されます。
スキーマには次の3つのタイプがあります。
Post
:ブログアプリの投稿のタイプを定義し、String
のように、各フィールドの後にそのタイプが続く4つのフィールドが含まれています。Query
:角かっこで示されるように複数の投稿を返すfeed
クエリと、単一の引数を受け入れて単一のPost
を返すpost
クエリを定義します。 。Mutation
:ドラフトPost
を作成するためのcreateDraft
ミューテーションと、id
を受け入れてPost
。
すべてのGraphQLAPIにはqueryタイプがあり、mutationタイプがある場合とない場合があることに注意してください。 これらのタイプは通常のオブジェクトタイプと同じですが、すべてのGraphQLクエリのエントリポイントを定義するため、特別です。
次に、posts
配列をsrc/schema.js
ファイルのtypeDefs
変数の下に追加します。
プリズム-graphql/src / schema.js
... const posts = [ { id: 1, title: 'Subscribe to GraphQL Weekly for community news ', content: 'https://graphqlweekly.com/', published: true, }, { id: 2, title: 'Follow DigitalOcean on Twitter', content: 'https://twitter.com/digitalocean', published: true, }, { id: 3, title: 'What is GraphQL?', content: 'GraphQL is a query language for APIs', published: false, }, ]
posts
配列は、3つの事前定義された投稿で定義します。 各post
オブジェクトの構造は、スキーマで定義したPost
タイプと一致することに注意してください。 この配列は、APIによって提供される投稿を保持します。 次のステップでは、データベースとPrisma Clientが導入されたら、アレイを置き換えます。
次に、定義したposts
配列の下にresolvers
オブジェクトを定義します。
プリズム-graphql/src / schema.js
... const resolvers = { Query: { feed: (parent, args) => { return posts.filter((post) => post.published) }, post: (parent, args) => { return posts.find((post) => post.id === Number(args.id)) }, }, Mutation: { createDraft: (parent, args) => { posts.push({ id: posts.length + 1, title: args.title, content: args.content, published: false, }) return posts[posts.length - 1] }, publish: (parent, args) => { const postToPublish = posts.find((post) => post.id === Number(args.id)) postToPublish.published = true return postToPublish }, }, Post: { content: (parent) => parent.content, id: (parent) => parent.id, published: (parent) => parent.published, title: (parent) => parent.title, }, } module.exports = { resolvers, typeDefs, }
GraphQLスキーマと同じ構造に従ってリゾルバーを定義します。 スキーマのタイプのすべてのフィールドには、対応するリゾルバー関数があり、その責任は、スキーマ内のそのフィールドのデータを返すことです。 たとえば、Query.feed()
リゾルバーは、posts
配列をフィルタリングすることにより、公開された投稿を返します。
リゾルバー関数は4つの引数を受け取ります。
parent
:親は、リゾルバーチェーン内の前のリゾルバーの戻り値です。 トップレベルのリゾルバーの場合、前のリゾルバーが呼び出されないため、親はundefined
です。 たとえば、feed
クエリを実行すると、query.feed()
リゾルバーがparent
の値undefined
で呼び出され、次に[のリゾルバーで呼び出されます。 X145X]が呼び出されます。ここで、parent
は、feed
リゾルバーから返されるオブジェクトです。args
:この引数は、クエリのパラメータを伝達します。たとえば、post
クエリは、フェッチされる投稿のid
を受け取ります。context
:各リゾルバーが書き込みおよび読み取りを行うことができるリゾルバーチェーンを通過するオブジェクト。これにより、リゾルバーは情報を共有できます。info
:クエリまたはミューテーションのAST表現。 このシリーズのパートIIIで詳細を読むことができます:GraphQLリゾルバーでの情報引数の謎解き。
これらのリゾルバではcontext
とinfo
は不要であるため、parent
とargs
のみが定義されています。
完了したら、ファイルを保存して終了します。
注: Post
の4つのリゾルバーのように、リゾルバーがリゾルバーの名前と同じフィールドを返すと、ApolloServerはそれらを自動的に解決します。 これは、これらのリゾルバーを明示的に定義する必要がないことを意味します。
- Post: { - content: (parent) => parent.content, - id: (parent) => parent.id, - published: (parent) => parent.published, - title: (parent) => parent.title, - },
最後に、スキーマとリゾルバーをエクスポートして、次のステップでそれらを使用してApolloServerでサーバーをインスタンス化できるようにします。
ステップ3—GraphQLサーバーを作成する
このステップでは、Apollo Serverを使用してGraphQLサーバーを作成し、サーバーが接続を受け入れることができるようにポートにバインドします。
まず、次のコマンドを実行して、サーバー用のファイルを作成します。
nano src/server.js
次に、次のコードをファイルに追加します。
プリズム-graphql/src / server.js
const { ApolloServer } = require('apollo-server') const { resolvers, typeDefs } = require('./schema') const port = process.env.PORT || 8080 new ApolloServer({ resolvers, typeDefs }).listen({ port }, () => console.log(`Server ready at: http://localhost:${port}`), )
ここでは、サーバーをインスタンス化し、前の手順のスキーマとリゾルバーを渡します。
サーバーがバインドするポートは、PORT
環境変数から設定され、設定されていない場合、デフォルトで8080
になります。 PORT
環境変数はAppPlatformによって自動的に設定され、サーバーがデプロイされると接続を受け入れることができるようにします。
ファイルを保存して終了します。
GraphQLAPIを実行する準備が整いました。 次のコマンドでサーバーを起動します。
node src/server.js
次の出力が表示されます。
OutputServer ready at: http://localhost:8080
サーバーへのエントリポイントが明確になるように、package.json
に開始スクリプトを追加することをお勧めします。 さらに、これにより、AppPlatformはデプロイ後にサーバーを起動できるようになります。
これを行うには、package.json
の"scripts"
オブジェクトに次の行を追加します。
package.json
{ "name": "prisma-graphql", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "node ./src/server.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "apollo-server": "^2.18.2", "graphql": "^15.3.0" } }
終了したら、ファイルを保存して終了します。
これで、次のコマンドを使用してサーバーを起動できます。
npm start
GraphQL APIをテストするには、出力からURLを開きます。これにより、GraphQLPlaygroundに移動します。
GraphQL Playgroundは、クエリとミューテーションを送信してAPIをテストできるIDEです。
たとえば、公開された投稿のみを返すfeed
クエリをテストするには、IDEの左側に次のクエリを入力し、再生ボタンを押してクエリを送信します。
query { feed { id title content published } }
応答には、Subscribe to GraphQL Weekly
のタイトルとそのURL、およびFollow DigitalOcean on Twitter
とそのURLが表示されます。
createDraft
ミューテーションをテストするには、次のミューテーションを入力します。
mutation { createDraft(title: "Deploying a GraphQL API to DigitalOcean") { id title content published } }
再生ボタンを使用してミューテーションを送信すると、応答の一部としてtitle
フィールド内にDeploying a GraphQL API to DigitalOcean
が表示されます。
注: createDraft
に続く中括弧内のフィールドを追加または削除することにより、ミューテーションから返すフィールドを選択できます。 たとえば、id
とtitle
のみを返したい場合は、次のミューテーションを送信できます。
mutation { createDraft(title: "Deploying a GraphQL API to DigitalOcean") { id title } }
これで、GraphQLサーバーが正常に作成およびテストされました。 次のステップでは、プロジェクトのGitHubリポジトリを作成します。
ステップ4—GitHubリポジトリを作成する
このステップでは、プロジェクトのGitHubリポジトリを作成し、変更をプッシュして、GraphQLAPIをGitHubからAppPlatformに自動的にデプロイできるようにします。
prisma-graphql
フォルダーからリポジトリを初期化することから始めます。
git init
次に、次の2つのコマンドを使用して、コードをリポジトリにコミットします。
git add src package-lock.json package.json git commit -m 'Initial commit'
変更がローカルリポジトリにコミットされたので、GitHubにリポジトリを作成し、変更をプッシュします。
GitHub に移動して、新しいリポジトリを作成します。 一貫性を保つために、リポジトリに prisma-graphql という名前を付けてから、リポジトリの作成をクリックします。
リポジトリが作成されたら、次のコマンドを使用して変更をプッシュします。これには、デフォルトのローカルブランチの名前をmain
に変更することも含まれます。
git remote add origin [email protected]:your_github_username/prisma-graphql.git git branch -M main git push --set-upstream origin main
変更を正常にコミットしてGitHubにプッシュしました。 次に、リポジトリをApp Platformに接続し、GraphQLAPIをデプロイします。
ステップ5—AppPlatformへのデプロイ
このステップでは、前のステップで作成したGitHubリポジトリをDigitalOceanに接続し、変更をGitHubにプッシュしたときにGraphQLAPIが自動的にデプロイされるようにAppPlatformを構成します。
まず、アプリプラットフォームに移動し、アプリの起動ボタンをクリックします。
GitHubアカウントをリンクするためのボタンが表示されます。
それをクリックすると、GitHubにリダイレクトされます。
クリックインストールと承認そして、DigitalOceanにリダイレクトされます。
リポジトリyour_github_username/prisma-graphql
を選択し、次へをクリックします。
アプリをデプロイするリージョンを選択し、次へをクリックします。
ここで、アプリの構成をカスタマイズできます。 コマンドの実行がnpm start
であることを確認してください。 デフォルトでは、AppPlatformはHTTPポートを8080
に設定します。これは、GraphQLサーバーをバインドするように構成したポートと同じです。
次へをクリックすると、プランを選択するように求められます。
Basic を選択し、 Launch BasicAppをクリックします。 アプリページにリダイレクトされ、初期展開の進行状況が表示されます。
ビルドが完了すると、アプリがデプロイされたことを示す通知が届きます。
これで、アプリ名の下のURLでデプロイされたGraphQLAPIにアクセスできます。 ondigitalocean.app
サブドメインの下にあります。 URLを開くと、チュートリアルのステップ3で行ったのと同じ方法でGraphQLPlaygroundが開きます。
これで、リポジトリがApp Platformに正常に接続され、GraphQLAPIがデプロイされました。 次に、アプリを進化させ、GraphQLAPIのメモリ内データをデータベースに置き換えます。
ステップ6—PostgreSQLでPrismaを設定する
これまでに作成したGraphQLAPIは、メモリ内のposts
配列を使用してデータを格納していました。 これは、サーバーが再起動すると、データへのすべての変更が失われることを意味します。 データが安全に保持されるようにするには、posts
アレイをPostgreSQLデータベースに置き換え、Prismaを使用してデータにアクセスします。
このステップでは、Prisma CLIをインストールし、最初のPrismaスキーマを作成し、Dockerを使用してローカルでPostgreSQLをセットアップし、Prismaをそれに接続します。
Prismaスキーマは、Prismaセットアップのメイン構成ファイルであり、データベーススキーマが含まれています。
次のコマンドを使用してPrismaCLIをインストールすることから始めます。
npm install --save-dev @prisma/cli
Prisma CLIは、データベース移行の実行やPrismaクライアントの生成などのデータベースワークフローを支援します。
次に、Dockerを使用してPostgreSQLデータベースをセットアップします。 次のコマンドを使用して、新しいDockerComposeファイルを作成します。
nano docker-compose.yml
次に、新しく作成したファイルに次のコードを追加します。
プリズム-graphql/docker-compose.yml
version: '3.8' services: postgres: image: postgres:10.3 restart: always environment: - POSTGRES_USER=test-user - POSTGRES_PASSWORD=test-password volumes: - postgres:/var/lib/postgresql/data ports: - '5432:5432' volumes: postgres:
このDockerCompose構成ファイルは、マシンで公式のPostgreSQLDockerイメージを起動する役割を果たします。 POSTGRES_USER
およびPOSTGRES_PASSWORD
環境変数は、スーパーユーザー(管理者権限を持つユーザー)の資格情報を設定します。 また、これらの資格情報を使用してPrismaをデータベースに接続します。 最後に、PostgreSQLがデータを保存するボリュームを定義し、マシンの5432
ポートをDockerコンテナの同じポートにバインドします。
ファイルを保存して終了します。
この設定が整ったら、次のコマンドを使用してPostgreSQLデータベースサーバーを起動します。
docker-compose up -d
次のコマンドを使用して、データベースサーバーが実行されていることを確認できます。
docker ps
これにより、次のようなものが出力されます。
OutputCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 198f9431bf73 postgres:10.3 "docker-entrypoint.s…" 45 seconds ago Up 11 seconds 0.0.0.0:5432->5432/tcp prisma-graphql_postgres_1
PostgreSQLコンテナが実行されている状態で、Prismaセットアップを作成できます。 PrismaCLIから次のコマンドを実行します。
npx prisma init
ベストプラクティスとして、PrismaCLIのすべての呼び出しの前にnpx
を付ける必要があることに注意してください。 これにより、ローカルインストールを使用していることが保証されます。
コマンドを実行した後、PrismaCLIはプロジェクトにprisma
という名前の新しいフォルダーを作成しました。 次の2つのファイルが含まれています。
schema.prisma
:Prismaプロジェクトのメイン構成ファイル(データモデルを含める)。.env
:データベース接続URLを定義するためのdotenvファイル。
Prismaがデータベースの場所を認識していることを確認するには、.env
ファイルを開きます。
nano prisma/.env
DATABASE_URL
環境変数を次のように調整します。
プリズム-graphql/prisma / .env
DATABASE_URL="postgresql://test-user:test-password@localhost:5432/my-blog?schema=public"
DockerComposeファイルで指定されているデータベースクレデンシャルtest-user
およびtest-password
を使用していることに注意してください。 接続URLの形式の詳細については、 Prismadocsにアクセスしてください。
これでPostgreSQLが正常に起動し、Prismaスキーマを使用してPrismaが構成されました。 次のステップでは、ブログのデータモデルを定義し、PrismaMigrateを使用してデータベーススキーマを作成します。
ステップ7—PrismaMigrateを使用したデータモデルの定義
次に、作成したPrismaスキーマファイルでデータモデルを定義します。 次に、このデータモデルは、Prisma Migrateを使用してデータベースにマップされます。これにより、データモデルに対応するテーブルを作成するためのSQLステートメントが生成および送信されます。
ブログを作成しているので、アプリケーションの主なエンティティはユーザーと投稿になります。 このステップでは、GraphQLスキーマのPost
タイプと同様の構造を持つPost
モデルを定義します。 後のステップで、アプリを進化させ、User
モデルを追加します。
注: GraphQL APIは、データベースの抽象化レイヤーと見なすことができます。 GraphQL APIを構築する場合、GraphQLスキーマがデータベーススキーマに非常に似ているのが一般的です。 ただし、抽象化として、2つのスキーマは必ずしも同じ構造である必要はないため、APIを介して公開するデータを制御できます。 これは、一部のデータがAPIレイヤーに対して機密または無関係であると見なされる可能性があるためです。
Prismaは、独自のデータモデリング言語を使用して、アプリケーションデータの形状を定義します。
package.json
が配置されているプロジェクトのフォルダーからschema.prisma
ファイルを開きます。
nano prisma/schema.prisma
注: pwd
コマンドを使用すると、現在の作業ディレクトリを出力するフォルダを端末から確認できます。 さらに、ls
コマンドを使用してファイルを一覧表示すると、ファイルシステムをナビゲートするのに役立ちます。
次のモデル定義を追加します。
プリズム-graphql/prisma / schema.prisma
... model Post { id Int @default(autoincrement()) @id title String content String? published Boolean @default(false) }
いくつかのフィールドを使用してPost
と呼ばれるモデルを定義しています。 モデルはデータベーステーブルにマップされます。 フィールドは、個々の列を表します。
id
フィールドには、次のフィールド属性があります。
@default(autoincrement())
:これは列の自動インクリメントのデフォルト値を設定します。@id
:これは列をテーブルの主キーとして設定します。
完了したら、ファイルを保存して終了します。
モデルが配置されたら、PrismaMigrateを使用してデータベースに対応するテーブルを作成できます。 これは、移行ファイルを作成して実行するmigrate dev
コマンドを使用して実行できます。
ターミナルを再度開き、次のコマンドを実行します。
npx prisma migrate dev --preview-feature --name "init" --skip-generate
これにより、次のようなものが出力されます。
OutputPostgreSQL database my-blog created at localhost:5432 Prisma Migrate created and applied the following migration(s) from new schema changes: migrations/ └─ 20201201110111_init/ └─ migration.sql Everything is now in sync.
このコマンドは、ファイルシステム上に新しい migration を作成し、それをデータベースに対して実行してデータベーススキーマを作成します。 コマンドに提供されるオプションの概要は次のとおりです。
--preview-feature
:PrismaMigrateは現在プレビュー状態であるため必須です。--name "init"
:移行の名前を指定します(ファイルシステム上に作成された移行フォルダーに名前を付けるために使用されます)。--skip-generate
:Prismaクライアントの生成をスキップします(これは次のステップで実行されます)。
これで、prisma/migrations
ディレクトリにSQL移行ファイルが追加されました。 このアプローチにより、データベーススキーマへの変更を追跡し、本番環境で同じデータベーススキーマを作成できます。
注:すでにmy-blog
データベースでPrismaMigrateを使用していて、prisma/migration
フォルダー内の移行とデータベーススキーマの間に不整合がある場合は、プロンプトが表示されます次の出力でデータベースをリセットします。
Output? We need to reset the PostgreSQL database "my-blog" at "localhost:5432". All data will be lost. Do you want to continue? › (y/N)
これを解決するには、y
と入力すると、データベースがリセットされます。 これにより、データベース内のすべてのデータが失われることに注意してください。
これで、データベーススキーマが作成されました。 次のステップでは、Prisma Clientをインストールし、GraphQLリゾルバーで使用します。
ステップ8—GraphQLリゾルバーでのPrismaクライアントの使用
Prisma Clientは、自動生成されたタイプセーフなオブジェクトリレーショナルマッパー(ORM)であり、Node.jsアプリケーションからデータベース内のデータをプログラムで読み書きするために使用できます。 このステップでは、プロジェクトにPrismaClientをインストールします。
ターミナルを再度開き、PrismaClientnpmパッケージをインストールします。
npm install @prisma/client
注: Prisma Clientは、Prismaスキーマに基づいてnode_modules
フォルダーにコードを生成することにより、豊富なオートコンプリートを提供します。 コードを生成するには、npx prisma generate
コマンドを使用します。 これは通常、新しい移行を作成して実行した後に行われます。 ただし、最初のインストールでは、postinstall
フックで自動的に生成されるため、これは必要ありません。
データベースとGraphQLスキーマが作成され、Prisma Clientがインストールされたら、GraphQLリゾルバーでPrisma Clientを使用して、データベースのデータを読み書きします。 これを行うには、これまでデータを保持するために使用していたposts
配列を置き換えます。
次のファイルを作成することから始めます。
nano src/db.js
以下を追加します。
プリズム-graphql/src / db.js
const { PrismaClient } = require('@prisma/client') module.exports = { prisma: new PrismaClient(), }
これにより、Prisma Clientがインポートされ、そのインスタンスが作成され、リゾルバーで使用するインスタンスがエクスポートされます。
次に、src/db.js
ファイルを保存して閉じます。
次に、prisma
インスタンスをsrc/schema.js
にインポートします。 これを行うには、src/schema.js
を開きます。
nano src/schema.js
次に、ファイルの先頭にある./db
からprisma
をインポートします。
プリズム-graphql/src / schema.js
const { prisma } = require('./db') ...
次に、posts
アレイを削除します。
プリズム-graphql/src / schema.js
-const posts = [ - { - id: 1, - title: 'Subscribe to GraphQL Weekly for community news ', - content: 'https://graphqlweekly.com/', - published: true, - }, - { - id: 2, - title: 'Follow DigitalOcean on Twitter', - content: 'https://twitter.com/digitalocean', - published: true, - }, - { - id: 3, - title: 'What is GraphQL?', - content: 'GraphQL is a query language for APIs', - published: false, - }, -]
次に、Query
リゾルバーを更新して、公開された投稿をデータベースからフェッチします。 resolvers.Query
オブジェクトを次のリゾルバーで更新します。
プリズム-graphql/src / schema.js
... const resolvers = { Query: { feed: (parent, args) => { return prisma.post.findMany({ where: { published: true }, }) }, post: (parent, args) => { return prisma.post.findOne({ where: { id: Number(args.id) }, }) }, },
ここでは、2つのPrismaClientクエリを使用しています。
findMany
:publish
フィールドがfalse
である投稿を取得します。findOne
:id
フィールドがid
GraphQL引数と等しい単一の投稿をフェッチします。
GraphQL仕様に従って、ID
タイプはString
と同じ方法でシリアル化されることに注意してください。 したがって、Prismaスキーマのid
はint
であるため、Number
に変換します。
次に、Mutation
リゾルバーを更新して、データベース内の投稿を保存および更新します。 resolvers.Mutation
オブジェクトを次のリゾルバーで更新します。
プリズム-graphql/src / schema.js
const resolvers = { ... Mutation: { createDraft: (parent, args) => { return prisma.post.create({ data: { title: args.title, content: args.content, }, }) }, publish: (parent, args) => { return prisma.post.update({ where: { id: Number(args.id), }, data: { published: true, }, }) }, }, }
2つのPrismaクライアントクエリを使用しています。
create
:Post
レコードを作成します。update
:id
がクエリ引数のフィールドと一致するPost
レコードの公開フィールドを更新します。
schema.js
は次のようになります。
プリズム-graphql/src / schema.js
const { gql } = require('apollo-server') const { prisma } = require('./db') const typeDefs = gql` type Post { content: String id: ID! published: Boolean! title: String! } type Query { feed: [Post!]! post(id: ID!): Post } type Mutation { createDraft(content: String, title: String!): Post! publish(id: ID!): Post } ` const resolvers = { Query: { feed: (parent, args) => { return prisma.post.findMany({ where: { published: true }, }) }, post: (parent, args) => { return prisma.post.findOne({ where: { id: Number(args.id) }, }) }, }, Mutation: { createDraft: (parent, args) => { return prisma.post.create({ data: { title: args.title, content: args.content, }, }) }, publish: (parent, args) => { return prisma.post.update({ where: { id: Number(args.id), }, data: { published: true, }, }) }, }, } module.exports = { resolvers, typeDefs, }
ファイルを保存して閉じます。
Prisma Clientを使用するようにリゾルバーを更新したので、サーバーを起動して、次のコマンドを使用してGraphQLAPIとデータベース間のデータのフローをテストします。
npm start
繰り返しになりますが、次の出力が表示されます。
OutputServer ready at: http://localhost:8080
出力からのアドレスでGraphQLプレイグラウンドを開き、ステップ3と同じクエリを使用してGraphQLAPIをテストします。
次に、変更をコミットして、変更をAppPlatformにデプロイできるようにします。
node_modules
フォルダーとprisma/.env
ファイルのコミットを回避するには、まず.gitignore
ファイルを作成します。
nano .gitignore
ファイルに以下を追加します。
プリズム-graphql/.gitignore
node_modules prisma/.env
ファイルを保存して終了します。
次に、次の2つのコマンドを実行して、変更をコミットします。
git add . git commit -m 'Add Prisma'
次に、AppPlatformのアプリにPostgreSQLデータベースを追加します。
ステップ9—AppPlatformでのPostgreSQLデータベースの作成と移行
このステップでは、PostgreSQLデータベースをAppPlatformのアプリに追加します。 次に、Prisma Migrateを使用して移行を実行し、デプロイされたデータベーススキーマがローカルデータベースと一致するようにします。
まず、 App Platformコンソールに移動し、手順5で作成したprisma-graphqlプロジェクトを選択します。
次に、コンポーネントタブに移動します。
+コンポーネントの作成をクリックし、データベースを選択すると、データベースを構成するページが表示されます。
Dev Database を選択し、 Create andAttachをクリックします。
コンポーネントページにリダイレクトされ、データベースを作成するためのプログレスバーが表示されます。
データベースが作成されたら、ローカルマシンからDigitalOcean上の本番データベースに対してデータベース移行を実行します。 移行を実行するには、ホストされているデータベースの接続文字列が必要です。 入手するには、[コンポーネント]タブのdbアイコンをクリックします。
接続の詳細で、ドロップダウンの接続文字列を選択し、データベースのURLをコピーします。これは次の構造になります。
postgresql://db:some_password@unique_identifier.db.ondigitalocean.com:25060/db?sslmode=require
次に、ターミナルで次のコマンドを実行し、DATABASE_URL
がコピーしたのと同じURLに設定されていることを確認します。
DATABASE_URL="postgresql://db:some_password@unique_identifier.db.ondigitalocean.com:25060/db?sslmode=require" npx prisma migrate deploy --preview-feature
これにより、PrismaMigrateを使用してライブデータベースに対して移行が実行されます。
移行が成功すると、次のメッセージが表示されます。
OutputPostgreSQL database db created at unique_identifier.db.ondigitalocean.com:25060 Prisma Migrate applied the following migration(s): migrations/ └─ 20201201110111_init/ └─ migration.sql
これで、DigitalOceanの本番データベースが正常に移行されました。これは、Prismaスキーマと一致します。
これで、次のコマンドを使用してGitの変更をプッシュすることでアプリをデプロイできます。
git push
注: App Platformは、実行時にDATABASE_URL
環境変数をアプリケーションで使用できるようにします。 Prisma Clientは、Prismaスキーマのdatasource
ブロックのenv("DATABASE_URL")
を使用してその環境変数を使用します。
これにより、ビルドが自動的にトリガーされます。 App Platformコンソールを開くと、Deployingプログレスバーが表示されます。
展開が成功すると、正常に展開されましたメッセージが表示されます。
これで、デプロイされたGraphQLAPIをデータベースでバックアップしました。 Live App を開くと、GraphQLPlaygroundに移動します。 手順3と同じクエリを使用してGraphQLAPIをテストします。
最後のステップでは、User
モデルを追加してGraphQLAPIを進化させます。
ステップ10—ユーザーモデルの追加
ブログ用のGraphQLAPIには、Post
という名前の単一のエンティティがあります。 このステップでは、Prismaスキーマで新しいモデルを定義し、新しいモデルを利用するようにGraphQLスキーマを適応させることにより、APIを進化させます。 Post
モデルに1対多の関係を持つUser
モデルを紹介します。 これにより、投稿の作成者を表し、各ユーザーに複数の投稿を関連付けることができます。 次に、GraphQLスキーマを進化させて、APIを介してユーザーを作成し、投稿をユーザーに関連付けることができるようにします。
まず、Prismaスキーマを開き、以下を追加します。
プリズム-graphql/prisma / schema.prisma
... model Post { id Int @id @default(autoincrement()) title String content String? published Boolean @default(false) author User? @relation(fields: [authorId], references: [id]) authorId Int? } model User { id Int @id @default(autoincrement()) email String @unique name String posts Post[] }
Prismaスキーマに以下を追加しました。
- ユーザーを表す
User
モデル。 author
とposts
の2つの関係フィールド。 関係フィールドは、Prismaレベルでモデル間の接続を定義し、データベースには存在しません。 これらのフィールドは、Prismaクライアントを生成し、Prismaクライアントとの関係にアクセスするために使用されます。@relation
属性によって参照されるauthorId
フィールド。 Prismaは、Post
とUser
を接続するためにデータベースに外部キーを作成します。
Post
モデルの作成者フィールドはオプションであることに注意してください。 つまり、ユーザーに関連付けられていない投稿を作成できるようになります。
完了したら、ファイルを保存して終了します。
次に、次のコマンドを使用して、移行をローカルで作成して適用します。
npx prisma migrate dev --preview-feature --name "add-user"
移行が成功すると、次のメッセージが表示されます。
OutputPrisma Migrate created and applied the following migration(s) from new schema changes: migrations/ └─ 20201201123056_add_user/ └─ migration.sql ✔ Generated Prisma Client to ./node_modules/@prisma/client in 53ms
このコマンドは、新しいテーブルとフィールドを利用できるようにPrismaClientも生成します。
次に、データベーススキーマがローカルデータベースと同じになるように、AppPlatformの本番データベースに対して移行を実行します。 ターミナルで次のコマンドを実行し、DATABASE_URL
をAppPlatformからの接続URLに設定します。
DATABASE_URL="postgresql://db:some_password@unique_identifier.db.ondigitalocean.com:25060/db?sslmode=require" npx prisma migrate deploy --preview-feature
あなたは以下を受け取ります:
OutputPrisma Migrate applied the following migration(s): migrations/ └─ 20201201123056_add_user/ └─ migration.sql
ここで、GraphQLスキーマとリゾルバーを更新して、更新されたデータベーススキーマを利用します。
src/schema.js
ファイルを開き、次のように更新typeDefs
を追加します。
プリズム-graphql/src / schema.js
... const typeDefs = gql` type User { email: String! id: ID! name: String posts: [Post!]! } type Post { content: String id: ID! published: Boolean! title: String! author: User } type Query { feed: [Post!]! post(id: ID!): Post } type Mutation { createUser(data: UserCreateInput!): User! createDraft(authorEmail: String, content: String, title: String!): Post! publish(id: ID!): Post } input UserCreateInput { email: String! name: String posts: [PostCreateWithoutAuthorInput!] } input PostCreateWithoutAuthorInput { content: String published: Boolean title: String! } ` ...
この更新されたコードでは、GraphQLスキーマに次の変更を追加しています。
User
タイプ。Post
の配列を返します。author
フィールドをPost
タイプに変更します。createUser
ミューテーション。これは、入力タイプとしてUserCreateInput
を想定しています。createUser
ミューテーションの一部として投稿を作成するためのUserCreateInput
入力で使用されるPostCreateWithoutAuthorInput
入力タイプ。createDraft
ミューテーションに対するauthorEmail
オプションの引数。
スキーマが更新されたら、スキーマに一致するようにリゾルバーを更新します。
resolvers
オブジェクトを次のように更新します。
プリズム-graphql/src / schema.js
... const resolvers = { Query: { feed: (parent, args) => { return prisma.post.findMany({ where: { published: true }, }) }, post: (parent, args) => { return prisma.post.findOne({ where: { id: Number(args.id) }, }) }, }, Mutation: { createDraft: (parent, args) => { return prisma.post.create({ data: { title: args.title, content: args.content, published: false, author: args.authorEmail && { connect: { email: args.authorEmail }, }, }, }) }, publish: (parent, args) => { return prisma.post.update({ where: { id: Number(args.id) }, data: { published: true, }, }) }, createUser: (parent, args) => { return prisma.user.create({ data: { email: args.data.email, name: args.data.name, posts: { create: args.data.posts, }, }, }) }, }, User: { posts: (parent, args) => { return prisma.user .findOne({ where: { id: parent.id }, }) .posts() }, }, Post: { author: (parent, args) => { return prisma.post .findOne({ where: { id: parent.id }, }) .author() }, }, }
リゾルバーへの変更を分解してみましょう。
createDraft
ミューテーションリゾルバーは、authorEmail
引数(渡された場合)を使用して、作成されたドラフトと既存のユーザーの間に関係を作成するようになりました。- 新しい
createUser
ミューテーションリゾルバーは、ネストされた書き込みを使用してユーザーと関連する投稿を作成します。 User.posts
およびPost.author
リゾルバーは、User
またはPost
が照会されました。 これらは、Prismaの FluentAPIを使用してリレーションをフェッチします。
ファイルを保存して終了します。
サーバーを起動して、GraphQLAPIをテストします。
npm start
次のGraphQLミューテーションを使用して、createUser
リゾルバーをテストすることから始めます。
mutation { createUser(data: { email: "[email protected]", name: "Natalia" }) { email id } }
これにより、ユーザーが作成されます。
次に、createDraft
リゾルバーを次のミューテーションでテストします。
mutation { createDraft( authorEmail: "[email protected]" title: "Deploying a GraphQL API to App Platform" ) { id title content published author { id name } } }
クエリの戻り値がPost
の場合はいつでも、author
をフェッチできることに注意してください。 この例では、Post.author
リゾルバーが呼び出されます。
最後に、変更をコミットしてプッシュし、APIをデプロイします。
git add . git commit -m "add user model" git push
Prisma Migrateを使用してデータベーススキーマを正常に進化させ、GraphQLAPIで新しいモデルを公開しました。
結論
この記事では、PrismaとGraphQLを使用してGraphQL APIを構築し、それをDigitalOceanのAppPlatformにデプロイしました。 ApolloServerを使用してGraphQLスキーマとリゾルバーを定義しました。 次に、GraphQLリゾルバーでPrisma Clientを使用して、PostgreSQLデータベースのデータを永続化およびクエリしました。
次のステップとして、個々のユーザーをフェッチするクエリと、既存のドラフトをユーザーに接続するミューテーションを使用して、GraphQLAPIをさらに拡張できます。
データベース内のデータの調査に興味がある場合は、 PrismaStudioをチェックしてください。 必ずPrismaドキュメントにアクセスして、Prismaのさまざまな側面について学び、prisma-examplesリポジトリですぐに実行できるサンプルプロジェクトを調べてください。
このプロジェクトのコードは、DigitalOceanCommunityリポジトリにあります。