ウェブサイト検索

Prismaを使用してGraphQL APIを構築し、's App Platformにデプロイする方法


著者たちは、「寄付のための執筆」プログラムの一環として、COVID-19救助基金と技術教育基金に寄付を行うことを選びました。

紹介

GraphQLは、スキーマ定義言語とクエリ言語で構成されたAPI用のクエリ言語であり、APIの消費者が必要なデータのみを取得して柔軟なクエリをサポートできるようにします。GraphQLは、開発者がiOS、Android、およびアプリのウェブバリアントなど、複数のクライアントの異なるニーズを満たしながらAPIを進化させることを可能にします。さらに、GraphQLスキーマはAPIに型安全性の程度を追加し、APIのドキュメントの一形式としても機能します。

Prismaは、3つの主要なツールを持つオープンソースのデータベースツールキットです。

  • Prisma Client: Node.jsおよびTypeScript用の自動生成され、型安全なクエリビルダーです。
  • Prisma Migrate: 宣言的データモデリングとマイグレーションシステム。
  • Prisma Studio: データベース内のデータを表示および編集するためのGUI。

Prismaは、アプリケーション開発者が複雑なデータベースのワークフロー(スキーマの移行や複雑なSQLクエリの作成など)に時間を費やすのではなく、価値を追加する機能の実装に集中できるように、データベースとの作業を容易にします。

このチュートリアルでは、GraphQLとPrismaを組み合わせて使用し、両者の役割が相互に補完し合うことを学びます。GraphQLは、フロントエンドやモバイルアプリなどのクライアントで使用するためのデータへの柔軟なインターフェースを提供します。GraphQLは特定のデータベースに結びついていません。ここでPrismaが登場し、データが保存されるデータベースとのインタラクションを処理します。

DigitalOceanのApp Platformは、インフラストラクチャについて心配することなく、アプリケーションをデプロイし、クラウドでデータベースをプロビジョニングするシームレスな方法を提供します。これにより、クラウドでアプリケーションを運用する際の運用上の負担が軽減されます。特に、日次バックアップと自動フェイルオーバー機能を備えたマネージドPostgreSQLデータベースを作成できるのが特徴です。App Platformは、デプロイを効率化するネイティブNode.jsサポートを提供しています。

Node.jsを使用してJavaScriptでブログアプリケーションのためのGraphQL APIを構築します。まず、Apollo Serverを使用して、メモリ内データ構造をバックエンドに持つGraphQL APIを構築します。その後、APIをDigitalOcean App Platformにデプロイします。最後に、Prismaを使用してメモリ内ストレージを置き換え、PostgreSQLデータベースにデータを永続化し、アプリケーションを再度デプロイします。

チュートリアルの最後には、HTTP経由で送信されたGraphQLリクエストを処理し、PostgreSQLデータベースに対してCRUD操作を行うNode.js GraphQL APIがDigitalOceanにデプロイされます。

このプロジェクトのコードはDigitalOcean Communityリポジトリにあります。

前提条件

このガイドを始める前に、以下のものが必要です:

  • GitHubアカウント。
  • DigitalOceanのアカウント。
  • コンピュータにGitがインストールされていること。コンピュータにGitをインストールして設定する方法については、「オープンソースへの貢献: Gitの始め方」チュートリアルを参照してください。
  • コンピュータにNode.jsバージョン14以上がインストールされている必要があります。Node.jsをインストールしてコンピュータにローカル開発環境を設定する方法については、チュートリアル「Node.jsのインストールとローカル開発環境の作成」に従ってください。
  • あなたのコンピュータにDockerがインストールされている(PostgreSQLデータベースをローカルで実行するため)。

JavaScript、Node.js、GraphQL、およびPostgreSQLの基本的な知識があると役立ちますが、このチュートリアルには必ずしも必要ではありません。

ステップ 1 — Node.js プロジェクトの作成

このステップでは、npmを使用してNode.jsプロジェクトをセットアップし、apollo-serverとgraphqlの依存関係をインストールします。このプロジェクトは、チュートリアルを通じて構築およびデプロイするGraphQL APIの基盤となります。

まず、プロジェクトのために新しいディレクトリを作成してください:

  1. mkdir prisma-graphql

次に、ディレクトリに移動して空のnpmプロジェクトを初期化します:

  1. cd prisma-graphql
  2. npm init --yes

このコマンドは、npmプロジェクトの設定ファイルとして使用される最小限のpackage.jsonファイルを作成します。

次の出力を受け取ります:

Wrote to /Users/your_username/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を設定する準備が整いました。

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

  1. npm install apollo-server graphql --save

このコマンドは、プロジェクトに依存関係として2つのパッケージをインストールします。

  • apollo-serverは、GraphQLリクエストがどのように解決され、データがどのように取得されるかを定義するために使用するHTTPライブラリです。
  • graphql は、GraphQL スキーマを構築するために使用するライブラリです。

プロジェクトを作成し、依存関係をインストールしました。次のステップでは、GraphQLスキーマを定義します。

ステップ2 — GraphQLスキーマとリゾルバーの定義

このステップでは、GraphQLのスキーマとそれに対応するリゾルバを定義します。スキーマは、APIが処理できる操作を定義します。リゾルバは、これらのリクエストをメモリ内のデータ構造を使用して処理するためのロジックを定義します。次のステップでは、これをデータベースクエリに置き換えます。

まず、新しいディレクトリsrcを作成し、そこにソースファイルを含めること。

  1. mkdir src

次に、スキーマ用のファイルを作成するために以下のコマンドを実行します:

  1. nano 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
  }
`

グラフQLスキーマは、gql タグ付けテンプレートを使って定義します。スキーマは、一緒にAPIに対して実行できるクエリの形状を定義する型定義のコレクションです(したがって、typeDefs)。これは、GraphQLスキーマ文字列をApolloが期待するフォーマットに変換します。

スキーマは三つのタイプを導入します:

  • Postはブログアプリの投稿タイプを定義し、各フィールドの後にその型が付いた4つのフィールドを含みます。例えば、Stringのように。
  • Queryは、複数の投稿を返すfeedクエリを定義し、角括弧で示され、postクエリは単一の引数を受け入れ、単一のPostを返します。
  • Mutationは、ドラフトPostを作成するためのcreateDraftミューテーションと、idを受け取りPostを返すpublishミューテーションを定義します。

すべてのGraphQL APIにはqueryタイプがあり、mutationタイプがある場合とない場合があります。これらのタイプは通常のオブジェクトタイプと同じですが、すべてのGraphQLクエリのエントリーポイントを定義するため特別です。

次に、typeDefs 変数の下に src/schema.js ファイルに posts 配列を追加します:

...
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,
  },
]

あなたは、3つの事前定義された投稿で posts 配列を定義します。各 post オブジェクトの構造は、スキーマで定義した Post タイプと一致します。この配列は、APIによって提供される投稿を保持します。次のステップでは、データベースとPrisma Clientが導入された後にその配列を置き換えます。

次に、先ほど定義したposts配列の下に、次のコードを追加してresolversオブジェクトを定義します:

...
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で呼び出され、その後Postのリゾルバが呼び出されます。この時、parentはfeedリゾルバから返されたオブジェクトです。
  • argsはクエリのためのパラメータを含んでいます。例えば、postクエリは取得される投稿のidを受け取ります。
  • context は、各リゾルバが書き込みと読み込みを行うことができるリゾルバチェーンを通じて渡されるオブジェクトであり、リゾルバが情報を共有できるようにします。
  • infoは、クエリまたはミューテーションのAST表現です。このPrismaシリーズのGraphQLの基本に関する詳細をもっと読むことができます。

これらのリゾルバーでは context および info は必要ないため、parent と args のみが定義されています。

作業が完了したら、ファイルを保存して終了します。

注意: リゾルバーがリゾルバーの名前と同じフィールドを返す場合、Post の4つのリゾルバーのように、Apollo Serverは自動的にそれらを解決します。つまり、それらのリゾルバーを明示的に定義する必要はありません。

-  Post: {
-    content: (parent) => parent.content,
-    id: (parent) => parent.id,
-    published: (parent) => parent.published,
-    title: (parent) => parent.title,
-  },

スキーマとリゾルバーをエクスポートして、次のステップでApollo Serverを使用してサーバーをインスタンス化できるようにします。

ステップ3 — GraphQLサーバーの作成

このステップでは、Apollo Serverを使ってGraphQLサーバーを作成し、サーバーが接続を受け入れられるようにポートにバインドします。

まず、サーバー用のファイルを作成するために次のコマンドを実行してください:

  1. nano 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 環境変数は、App Platform によって自動的に設定され、デプロイされた後にサーバーが接続を受け入れられることを保証します。

ファイルを保存して終了します。

あなたのGraphQL APIは実行する準備が整いました。次のコマンドでサーバーを起動してください:

  1. node src/server.js

次の出力を受け取ります:

Server ready at: http://localhost:8080

サーバーのエントリポイントを明確にするために、package.json ファイルにスタートスクリプトを追加することは良いプラクティスと見なされます。これを行うことで、App Platformはデプロイ後にサーバーを起動できるようになります。

まず、CTRL+Cを押してサーバーを停止します。それから、開始スクリプトを追加するために、package.jsonファイルを開きます:

  1. nano 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": "^3.11.1",
    "graphql": "^16.6.0"
  }
}

ファイルを保存して終了します。

これから次のコマンドでサーバーを起動できます:

  1. npm start

次の出力を受け取ります:

> prisma-graphql@1.0.0 start
> node ./src/server.js

Server ready at: http://localhost:8080

GraphQL APIをテストするには、出力からURLを開いて、Apollo GraphQL Studioにアクセスしてください。ホームページでサーバーにクエリを実行ボタンをクリックして、IDEと対話します。

アポロ GraphQL スタジオ

Apollo GraphQL Studioは、クエリやミューテーションを送信してAPIをテストできるIDEです。

例えば、公開された投稿のみを返すfeedクエリをテストするには、IDEの左側に以下のクエリを入力し、実行ボタンまたは再生ボタンを押してクエリを送信します:

query {
  feed {
    id
    title
    content
    published
  }
}

レスポンスには、GraphQL Weeklyを購読のタイトルとそのURL、DigitalOceanをTwitterでフォローのタイトルとそのURLが表示されます。

GraphQL フィードクエリ

前のクエリの上にあるバーの+ボタンをクリックして新しいタブを作成します。次に、createDraftミューテーションをテストするために、次のミューテーションを入力します:

mutation {
  createDraft(title: "Deploying a GraphQL API to DigitalOcean") {
    id
    title
    content
    published
  }
}

プレイボタンを使用して変異を送信すると、レスポンスの一部としてtitleフィールドにDigitalOceanへのGraphQL APIのデプロイというレスポンスが返されます。

GraphQLドラフト作成ミューテーション

注: createDraft の後の中括弧 ({}) 内でフィールドを追加または削除することで、ミューテーションから返すフィールドを選択できます。たとえば、id と title のみを返してほしい場合は、以下のミューテーションを送信できます:

mutation {
  createDraft(title: "Deploying a GraphQL API to DigitalOcean") {
    id
    title
  }
}

あなたはGraphQLサーバーを正常に作成し、テストしました。次のステップでは、このプロジェクトのためにGitHubリポジトリを作成します。

ステップ4 — GitHubリポジトリの作成

このステップでは、プロジェクトのためにGitHubリポジトリを作成し、変更をプッシュして、GraphQL APIが自動的にGitHubからApp Platformにデプロイできるようにします。

まず、CTRL+Cを押して開発サーバーを停止します。次に、以下のコマンドを使用してprisma-graphqlフォルダーからリポジトリを初期化します:

  1. git init

次に、以下の2つのコマンドを使用してコードをリポジトリにコミットします:

  1. git add src package-lock.json package.json
  2. git commit -m 'Initial commit'

変更がローカルリポジトリにコミットされたので、GitHubにリポジトリを作成し、変更をプッシュします。

GitHubに移動して新しいリポジトリを作成します。一貫性のためにリポジトリの名前をprisma-graphqlとし、次にリポジトリを作成をクリックします。

リポジトリが作成されたら、以下のコマンドを使用して変更をプッシュします。このコマンドには、デフォルトのローカルブランチの名前をmainに変更することが含まれています:

  1. git remote add origin git@github.com:your_github_username/prisma-graphql.git
  2. git branch -M main
  3. git push --set-upstream origin main

あなたは変更を無事にコミットし、GitHubにプッシュしました。次に、リポジトリをApp Platformに接続し、GraphQL APIをデプロイします。

ステップ5 — アプリプラットフォームへのデプロイ

このステップでは、あなたが作成したGitHubリポジトリをDigitalOceanに接続し、App Platformを設定して、GitHubに変更をプッシュするたびにGraphQL APIが自動的にデプロイされるようにします。

まず、DigitalOcean Cloud ConsoleのApp Platformページにアクセスし、アプリを作成ボタンをクリックします。

サービスプロバイダーのオプションが表示され、GitHubがデフォルトとして設定されます。

DigitalOceanをあなたのGitHubアカウントに設定していない場合は、アクセスを管理ボタンをクリックして、GitHubにリダイレクトされてください。

GitHubアクセスを管理する

すべてのリポジトリまたは特定のリポジトリを選択できます。インストールおよび承認をクリックすると、DigitalOcean App Platformの作成に戻されます。

リポジトリyour_github_username/prisma-graphqlを選択し、次へをクリックします。自動デプロイはデフォルトで選択されており、再デプロイの一貫性のためにそのまま選択したままにしておくことができます。

リポジトリを選択

リソースページで、プランを編集ボタンをクリックして、適切なプランを選択します。必要なプランサイズのベーシックプランを選んでください(このチュートリアルでは$5.00/月 - ベーシックプランを使用します)。

支払いプランを編集

次に、戻るを押して作成ページに戻ります。

プロジェクト名の隣にあるペンアイコンを押すと、アプリの設定をカスタマイズできます。アプリケーション設定ページが開きます:

アプリケーション設定

Run Commandをnpm startに設定してください。デフォルトでは、App PlatformはHTTPポートを8080に設定します。これは、あなたがGraphQLサーバーにバインドするように設定したのと同じポートです。

設定のカスタマイズが完了したら、戻るボタンを押して設定ページに戻ります。その後、次へボタンを押して環境変数ページに移動します。

あなたの環境変数は、現時点でさらなる設定は必要ありません。次へボタンをクリックしてください。

環境変数

情報ページでは、アプリの詳細と位置情報を調整できます。アプリ情報を編集して、アプリを展開したい地域を選択してください。保存ボタンを押してアプリの詳細を確認します。それから、次へボタンをクリックしてください。

申請の場所

選択したすべてのオプションは、レビューページで確認できます。その後、リソースを作成をクリックしてください。初期デプロイメントの進捗が表示されるアプリのページにリダイレクトされます。

ビルドが完了すると、アプリがデプロイされたことを示す通知が届きます。

デプロイメント進捗バー

あなたは今、DigitalOcean Consoleのアプリ名の下にあるURLで、デプロイされたGraphQL APIを訪問することができます。それはondigitalocean.appサブドメインを介してリンクされます。URLを開くと、GraphQL Playgroundはこのチュートリアルのステップ3と同じように開きます。

あなたはリポジトリをApp Platformに正常に接続し、GraphQL APIをデプロイしました。次に、アプリを進化させ、GraphQL APIのメモリ内データをデータベースで置き換えます。

ステップ6 — PostgreSQLとのPrismaの設定

これまでのところ、メモリ内のposts配列を使用してデータを保存するGraphQL APIを構築しました。サーバーが再起動すると、データに対するすべての変更が失われます。データが安全に永続化されることを保証するために、posts配列をPostgreSQLデータベースに置き換え、Prismaを使用してデータにアクセスします。

このステップでは、Prisma CLIをインストールし、初期のPrismaスキーマ(Prismaセットアップのための主要な構成ファイルで、データベーススキーマを含みます)を作成し、Dockerを使用してPostgreSQLをローカルにセットアップし、Prismaをそれに接続します。

次のコマンドでPrisma CLIをインストールします:

  1. npm install --save-dev prisma

Prisma CLIは、データベースマイグレーションの実行やPrisma Clientの生成などのデータベースワークフローを支援します。

次に、Dockerを使用してPostgreSQLデータベースをセットアップします。次のコマンドで新しいDocker Composeファイルを作成してください。

  1. nano docker-compose.yml

新しく作成したファイルに以下のコードを追加してください:

version: '3.8'
services:
  postgres:
    image: postgres:14
    restart: always
    environment:
      - POSTGRES_USER=test-user
      - POSTGRES_PASSWORD=test-password
    volumes:
      - postgres:/var/lib/postgresql/data
    ports:
      - '5432:5432'
volumes:
  postgres:

このDocker Compose構成ファイルは、あなたのマシン上で公式のPostgreSQL Dockerイメージを起動するためのものです。POSTGRES_USERとPOSTGRES_PASSWORD環境変数はスーパーユーザー(管理者権限を持つユーザー)の資格情報を設定します。また、これらの資格情報を使用してPrismaをデータベースに接続します。test-userとtest-passwordをあなたのユーザー資格情報に置き換えてください。

最後に、PostgreSQLがデータを保存するボリュームを定義し、あなたのマシンの5432ポートをDockerコンテナ内の同じポートにバインドします。

ファイルを保存して終了します。

この設定が整ったことで、以下のコマンドでPostgreSQLデータベースサーバーを起動できます:

  1. docker-compose up -d

読み込むのに数分かかる場合があります。

次のコマンドでデータベースサーバーが実行中であることを確認できます。

  1. docker ps

このコマンドは次のような出力をします:

CONTAINER 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のセットアップを作成できます。Prisma CLIから以下のコマンドを実行してください:

  1. npx prisma init

ベストプラクティスとして、Prisma CLIのすべての呼び出しにはnpxを前に付けて、ローカルインストールを使用することを保証するべきです。

このような出力が表示されます:

✔ Your Prisma schema was created at prisma/schema.prisma
  You can now open it in your favorite editor.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb.
3. Run prisma db pull to turn your database schema into a Prisma schema.
4. Run prisma generate to generate the Prisma Client. You can then start querying your database.

More information in our documentation:
https://pris.ly/d/getting-started

コマンドを実行すると、Prisma CLIはプロジェクトフォルダ内にデータベース接続URLを定義するための.envという名前のdotenvファイルと、schema.prismaファイルが含まれるprismaという新しいネストされたフォルダを生成します。これは、データモデルを含めるPrismaプロジェクトのメイン設定ファイルです。

Prismaがデータベースの位置を認識するようにするため、.envファイルを開いてください:

  1. nano .env

DATABASE_URL 環境変数をユーザーの認証情報で調整してください:

DATABASE_URL="postgresql://test-user:test-password@localhost:5432/my-blog?schema=public"

あなたは、Docker Composeファイルに記載されているデータベースの認証情報test-userおよびtest-passwordを使用します。Docker Composeファイルで認証情報を変更した場合は、この行を更新してそのファイルの認証情報と一致させることを確認してください。接続URLの形式について詳しくは、Prismaのドキュメントをご覧ください。

PostgreSQLを正常に起動し、Prismaスキーマを使用してPrismaを構成しました。次のステップでは、ブログのデータモデルを定義し、Prisma Migrateを使用してデータベーススキーマを作成します。

ステップ 7 — Prisma Migrate を使ったデータモデルの定義

今、あなたは先ほど作成したPrismaスキーマファイルでデータモデルを定義します。このデータモデルはPrisma Migrateによってデータベースにマッピングされ、データモデルに対応するテーブルを作成するためのSQL文を生成して送信します。

ブログを作成しているので、アプリケーションの主なエンティティはユーザーと投稿になります。このステップでは、GraphQLスキーマのPostタイプに似た構造のPostモデルを定義します。後のステップでは、アプリを発展させてUserモデルを追加します。

注意:GraphQL APIは、データベースの抽象化層と見なすことができます。GraphQL APIを構築する際、GraphQLスキーマはデータベーススキーマに非常に似ていることが一般的です。しかし、抽象化であるため、2つのスキーマは必ずしも同じ構造を持つわけではなく、APIを介して公開したいデータを制御することが可能です。なぜなら、あるデータはAPI層にとって敏感または無関係と見なされる場合があるからです。

Prismaは、アプリケーションデータの形状を定義するために独自のデータモデリング言語を使用します。

プロジェクトのフォルダから package.json がある場所にある schema.prisma ファイルを開いてください:

  1. nano prisma/schema.prisma

注意:あなたがどのフォルダーにいるかを確認するには、pwdコマンドを使ってターミナルで確認できます。これにより、現在の作業ディレクトリが出力されます。さらに、lsコマンドを使ってファイルをリスト表示すれば、ファイルシステムをナビゲートするのに役立ちます。

次のモデル定義を追加してください:

...
model Post {
  id        Int     @default(autoincrement()) @id
  title     String
  content   String?
  published Boolean @default(false)
}

あなたは、いくつかのフィールドを持つ Post というモデルを定義します。このモデルはデータベースの テーブル にマッピングされ、フィールドは個々の カラム を表します。

idフィールドには以下のフィールド属性があります:

  • @default(autoincrement()) は、その列に自動増分のデフォルト値を設定します。
  • @id はテーブルの主キーとして列を設定します。

ファイルを保存して終了します。

モデルが整ったら、migrate devコマンドを使用してPrisma Migrateでデータベースに対応するテーブルを作成し、マイグレーションファイルを作成および実行できます。

ターミナルで次のコマンドを実行してください:

  1. npx prisma migrate dev --name init --skip-generate

このコマンドは、ファイルシステム上に新しいマイグレーションを作成し、それをデータベースに対して実行してデータベーススキーマを作成します。--name initフラグはマイグレーションの名前を指定します(これはファイルシステム上に作成されるマイグレーションフォルダーの名前として使用されます)。--skip-generateフラグはPrisma Clientの生成をスキップします(これは次のステップで行われます)。

このコマンドは次のような出力をします:

Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "my-blog", schema "public" at "localhost:5432"

PostgreSQL database my-blog created at localhost:5432

Applying migration `20201201110111_init`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20201201110111_init/
    └─ migration.sql

Your database is now in sync with your schema.

あなたのprisma/migrationsディレクトリには、SQLマイグレーションファイルが現在追加されています。このアプローチにより、データベーススキーマの変更を追跡し、本番環境で同じデータベーススキーマを作成することができます。

注意: もしすでに my-blog データベースで Prisma Migrate を使用していて、prisma/migration フォルダ内のマイグレーションとデータベースのスキーマに不整合がある場合、以下の出力が表示されてデータベースをリセットするように促されます:

? 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アプリケーションからデータベースにプログラム的にデータを読み書きするために使用できます。このステップでは、プロジェクトにPrisma Clientをインストールします。

ターミナルで、Prisma Client npmパッケージをインストールしてください:

  1. npm install @prisma/client

注意: Prisma Clientは、Prismaスキーマに基づいてコードを生成し、node_modulesフォルダーに配置することで、豊富なオートコンプリート機能を提供します。コードを生成するには、npx prisma generateコマンドを使用します。これは通常、新しいマイグレーションを作成して実行した後に行います。ただし、最初のインストール時には、postinstallフックで自動的に生成されるため、これは必要ありません。

データベースとGraphQLスキーマを作成し、Prisma Clientをインストールした後、GraphQLリゾルバ内でPrisma Clientを使用してデータベースのデータを読み書きします。今までデータを保持するために使用していたposts配列を置き換えることでこれを行います。

次のファイルを作成して開いてください:

  1. nano src/db.js

新しいファイルに次の行を追加してください:

const { PrismaClient } = require('@prisma/client')

module.exports = {
  prisma: new PrismaClient(),
}

このコードはPrisma Clientをインポートし、そのインスタンスを作成し、リゾルバーで使用するインスタンスをエクスポートします。

今、src/db.jsファイルを保存して閉じてください。

次に、src/schema.jsにprismaインスタンスをインポートします。そのために、src/schema.jsを開いてください:

  1. nano src/schema.js

ファイルの先頭に ./db から prisma をインポートするこの行を追加してください:

const { prisma } = require('./db')
...

次に、ハイフン記号(-)でマークされた行を削除して、posts配列を削除してください:

...
-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 の既存の行を削除し、その後、強調表示された行を追加してオブジェクトを更新してください。

...
const resolvers = {
  Query: {
    feed: (parent, args) => {
      return prisma.post.findMany({
        where: { published: true },
      })
    },
    post: (parent, args) => {
      return prisma.post.findUnique({
        where: { id: Number(args.id) },
      })
    },
  },
...

ここでは、2つのPrisma Clientクエリを使用します。

  • findMany は publish フィールドが false である投稿を取得します。
  • findUnique は、id フィールドが id GraphQL 引数に等しい単一の投稿を取得します。

GraphQLの仕様に従い、IDタイプはStringと同じ方法でシリアライズされます。したがって、Prismaスキーマのidがintであるため、Numberに変換します。

次に、Mutation リゾルバーを更新して、データベースに投稿を保存および更新します。まず、resolvers.Mutation オブジェクトのコードと Number(args.id) の行を削除し、次に強調表示された行を追加してください:

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 Clientクエリを使用しています。

  • create は Post レコードを作成するためのものです。
  • updateを使用して、クエリ引数にあるidと一致するPostレコードの公開フィールドを更新します。

最後に、resolvers.Postオブジェクトを削除します:

...
-Post: {
-  content: (parent) => parent.content,
-  id: (parent) => parent.id,
-  published: (parent) => parent.published,
-  title: (parent) => parent.title,
-},
...

あなたの 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.findUnique({
        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を使うためにリゾルバーを更新したので、次のコマンドを使ってサーバーを起動し、GraphQL APIとデータベース間のデータの流れをテストしてください:

  1. npm start

もう一度、次の出力を受け取ります:

Server ready at: http://localhost:8080

出力からのアドレスでApollo GraphQL Studioを開き、ステップ3で使用したのと同じクエリを使ってGraphQL APIをテストします。

今、変更をコミットして、変更をアプリプラットフォームにデプロイできるようにします。CTRL+CでApolloサーバーを停止してください。

「node_modules」フォルダーと「.env」ファイルをコミットしないようにするために、プロジェクトフォルダー内の「.gitignore」ファイルを確認してください:

  1. cat .gitignore

.gitignoreファイルにこれらの行が含まれていることを確認してください:

node_modules
.env

もしそうでない場合は、ファイルを一致するように更新してください。

ファイルを保存して終了します。

次に、変更をコミットするために以下の2つのコマンドを実行してください:

  1. git add .
  2. git commit -m 'Add Prisma'

このような出力応答が得られます:

git commit -m 'Add Prisma'
[main 1646d07] Add Prisma
 9 files changed, 157 insertions(+), 39 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 docker-compose.yml
 create mode 100644 prisma/migrations/20201201110111_init/migration.sql
 create mode 100644 prisma/migrations/migration_lock.toml
 create mode 100644 prisma/schema.prisma
 create mode 100644 src/db.js

あなたは、GraphQLリゾルバーをPrisma Clientを使用してデータベースへのクエリとミューテーションを行うように更新し、すべての変更をリモートリポジトリにコミットしました。次に、App PlatformにアプリにPostgreSQLデータベースを追加します。

ステップ9 — App PlatformでのPostgreSQLデータベースの作成と移行

このステップでは、App PlatformにPostgreSQLデータベースを追加します。そして、Prisma Migrateを使用してマイグレーションを実行し、デプロイされたデータベーススキーマがローカルデータベースと一致するようにします。

まず、App Platformコンソールにアクセスし、ステップ5で作成したprisma-graphqlプロジェクトを選択してください。

次に、作成ボタンをクリックし、ドロップダウンメニューからデータベースを作成/添付を選択します。これにより、データベースを設定するためのページに移動します。

データベースを作成/添付する

Dev Databaseを選択し、名前を選んで作成して割り当てるをクリックします。

データベース設定

データベースを作成するための進捗バーがあるプロジェクトビューにリダイレクトされます。

プログレスバー

データベースが作成された後、ローカルマシンからDigitalOceanの本番データベースに対してデータベースマイグレーションを実行します。マイグレーションを実行するには、ホストされたデータベースの接続文字列が必要です。

それを得るには、設定タブのコンポーネントセクションにあるdbアイコンをクリックしてください。

データベースコンポーネント設定

接続の詳細の下にある表示を押し、ドロップダウンメニューから接続文字列を選択します。次の構造を持つデータベースのURLをコピーします:

postgresql://db:some_password@unique_identifier.db.onlinux-console.net:25060/db?sslmode=require

次に、ターミナルで次のコマンドを実行してください。その際、your_db_connection_stringを先ほどコピーしたURLに設定することを忘れないでください:

  1. DATABASE_URL="your_db_connection_string" npx prisma migrate deploy

このコマンドは、Prisma Migrateを使用して、ライブデータベースに対してマイグレーションを実行します。

移行が成功した場合、次の出力が得られます:

PostgreSQL database db created at unique_identifier.db.onlinux-console.net:25060

Prisma Migrate applied the following migration(s):

migrations/
  └─ 20201201110111_init/
    └─ migration.sql

あなたはDigitalOcean上で本番データベースを正常に移行しました。これでPrismaスキーマと一致しています。

注意: 次のエラーメッセージが表示された場合:

Error: P1001: Can't reach database server at `unique_identifier.db.onlinux-console.net`:`25060`

データベースがプロビジョニングされていることを確認するために、データベースダッシュボードに移動します。データベースの信頼されたソースを更新または無効にする必要があるかもしれません。

これで、次のコマンドを使ってGitの変更をプッシュすることでアプリをデプロイできます:

  1. git push

注: アプリプラットフォームは、実行時にアプリケーションにDATABASE_URL環境変数を提供します。Prisma Clientは、Prismaスキーマのdatasourceブロック内のenv("DATABASE_URL")とともにその環境変数を使用します。

これにより、自動的にビルドがトリガーされます。アプリプラットフォームのコンソールを開くと、デプロイメントの進捗バーが表示されます。

デプロイメント進捗バー

デプロイメントが成功すると、デプロイメントが開始されましたというメッセージが届きます。

デプロイされたGraphQL APIをデータベースでバックアップしました。ライブアプリを開くと、Apollo GraphQLスタジオに移動します。ステップ3と同じクエリを使用してGraphQL APIをテストしてください。

最終ステップでは、Userモデルを追加してGraphQL APIを進化させます。

ステップ10 — ユーザーモデルの追加

ブログ用のGraphQL APIには、Postという単一のエンティティがあります。このステップでは、Prismaスキーマに新しいモデルを定義し、その新しいモデルを利用するようにGraphQLスキーマを適応させることでAPIを進化させます。Userモデルを導入し、Postモデルとの一対多の関係を作成することで、投稿の著者を表現し、それぞれのユーザーに複数の投稿を関連付けることができるようにします。次に、APIを通じてユーザーの作成と投稿のユーザーとの関連付けを可能にするために、GraphQLスキーマを進化させます。

まず、Prismaスキーマを開いてください。

  1. nano prisma/schema.prisma

ハイライトされた行を追加して、Post モデルに authorId フィールドを追加し、User モデルを定義します:

...
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スキーマに追加されました:

  • 2つのリレーションフィールド:authorとposts。リレーションフィールドは、Prismaレベルでのモデル間の接続を定義し、データベースには存在しません。これらのフィールドはPrisma Clientを生成し、Prisma Clientを使用してリレーションにアクセスするために使用されます。
  • authorIdフィールドは、@relation属性によって参照されます。Prismaは、PostとUserを接続するためにデータベースに外部キーを作成します。
  • Userモデルはユーザーを表現します。

Postモデルのauthorフィールドはオプションですが、ユーザーに関連付けられていない投稿を作成することができます。

完了したらファイルを保存して終了してください。

次に、以下のコマンドを使ってマイグレーションをローカルで作成し、適用します:

  1. npx prisma migrate dev --name "add-user"

移行が成功した場合、次のメッセージが表示されます:

Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "my-blog", schema "public" at "localhost:5432"

Applying migration `20201201123056_add_user`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20201201123056_add_user/
    └─ migration.sql

Your database is now in sync with your schema.

✔ Generated Prisma Client (4.6.1 | library) to ./node_modules/@prisma/client in 53ms

このコマンドはPrisma Clientも生成するので、新しいテーブルとフィールドを利用できます。

今度はApp Platform上の本番データベースに対してマイグレーションを実行し、データベーススキーマがローカルデータベースと同じになるようにします。ターミナルで以下のコマンドを実行し、DATABASE_URLをApp Platformからの接続URLに設定してください:

  1. DATABASE_URL="your_db_connection_string" npx prisma migrate deploy

次の出力を受け取ります:

Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "db", schema "public" at "unique_identifier.db.onlinux-console.net:25060"

2 migrations found in prisma/migrations

Applying migration `20201201123056_add_user`

The following migration have been applied:

migrations/
  └─ 20201201123056_add_user/
    └─ migration.sql
      
All migrations have been successfully applied.

これから更新されたデータベーススキーマを利用するために、GraphQLスキーマとリゾルバーを更新します。

src/schema.jsファイルを開いてください:

  1. nano src/schema.js

次のように強調された行でtypeDefsを更新します:

...
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の配列を返します。
  • 投稿タイプの著者フィールド。
  • createUserミューテーションは、入力タイプとしてUserCreateInputを期待しています。
  • createUser ミューテーションの一部として投稿を作成するための UserCreateInput 入力で使用される PostCreateWithoutAuthorInput 入力タイプ。
  • createDraft ミューテーションの authorEmail オプショナル引数。

スキーマが更新されたので、スキーマに合わせてリゾルバーを更新します。

以下のように強調された行でresolversオブジェクトを更新してください:

...
const resolvers = {
  Query: {
    feed: (parent, args) => {
      return prisma.post.findMany({
        where: { published: true },
      })
    },
    post: (parent, args) => {
      return prisma.post.findUnique({
        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
        .findUnique({
          where: { id: parent.id },
        })
        .posts()
    },
  },
  Post: {
    author: (parent, args) => {
      return prisma.post
        .findUnique({
          where: { id: parent.id },
        })
        .author()
    },
  },
}
...

createDraft ミューテーションリゾルバーは、渡された場合に authorEmail 引数を使用して、作成されたドラフトと既存のユーザーとの関係を作成します。

新しい createUser ミューテーションリゾルバーは、ネストされた書き込みを使用してユーザーと関連する投稿を作成します。

User.posts および Post.author リゾルバーは、User または Post がクエリされるときに posts および author フィールドをどのように解決するかを定義します。これらは、Prisma のフルーエント API を使用して関連を取得します。

ファイルを保存して終了します。

サーバーを起動してGraphQL APIをテストします:

  1. npm start

次に、以下のGraphQLミューテーションでcreateUserリゾルバーをテストしてください。

mutation {
  createUser(data: { email: "natalia@prisma.io", name: "Natalia" }) {
    email
    id
  }
}

この変異はユーザーを作成します。

次に、以下のミューテーションを使ってcreateDraftリゾルバーをテストします:

mutation {
  createDraft(
    authorEmail: "natalia@prisma.io"
    title: "Deploying a GraphQL API to App Platform"
  ) {
    id
    title
    content
    published
    author {
      id
      name
    }
  }
}

クエリの戻り値が Post の場合、author を取得できます。この例では、Post.author リゾルバーが呼び出されます。

テストが完了したらサーバーを閉じてください。

それから変更をコミットして、APIをデプロイするためにプッシュしてください:

  1. git add .
  2. git commit -m "add user model"
  3. git push

更新が展開されるまでに数分かかる場合があります。

Prisma Migrateを使用してデータベーススキーマを正常に進化させ、新しいモデルをGraphQL APIで公開しました。

結論

この記事では、Prismaを使ってGraphQL APIを構築し、それをDigitalOceanのApp Platformにデプロイしました。Apollo Serverを使用してGraphQLスキーマとリゾルバーを定義しました。その後、Prisma ClientをGraphQLリゾルバーで使用して、PostgreSQLデータベースにデータを永続化し、クエリを実行しました。次のステップとして、個々のユーザーを取得するクエリと、既存のドラフトをユーザーに接続するミューテーションでGraphQL APIを拡張できます。

データベース内のデータを探索してみたい場合は、Prisma Studioをチェックしてください。また、Prismaのさまざまな側面について学ぶためにPrismaのドキュメントを訪れることもできますし、prisma-examplesリポジトリにある実行可能なサンプルプロジェクトも探索できます。

このプロジェクトのコードはDigitalOceanコミュニティのリポジトリにあります。

関連記事: