Prismaを使用してGraphQLAPIを構築し、DigitalOceanのアプリプラットフォームにデプロイする方法

提供:Dev Guides
移動先:案内検索

著者は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リポジトリにあります。

前提条件

このガイドを開始する前に、次のものが必要です。

JavaScriptNode.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リゾルバーでの情報引数の謎解き

これらのリゾルバではcontextinfoは不要であるため、parentargsのみが定義されています。

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

注: 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に続く中括弧内のフィールドを追加または削除することにより、ミューテーションから返すフィールドを選択できます。 たとえば、idtitleのみを返したい場合は、次のミューテーションを送信できます。

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

  • findManypublishフィールドがfalseである投稿を取得します。
  • findOneidフィールドがidGraphQL引数と等しい単一の投稿をフェッチします。

GraphQL仕様に従って、IDタイプはStringと同じ方法でシリアル化されることに注意してください。 したがって、Prismaスキーマのidintであるため、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クライアントクエリを使用しています。

  • createPostレコードを作成します。
  • updateidがクエリ引数のフィールドと一致する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モデル。
  • authorpostsの2つの関係フィールド。 関係フィールドは、Prismaレベルでモデル間の接続を定義し、データベースには存在しません。 これらのフィールドは、Prismaクライアントを生成し、Prismaクライアントとの関係にアクセスするために使用されます。
  • @relation属性によって参照されるauthorIdフィールド。 Prismaは、PostUserを接続するためにデータベースに外部キーを作成します。

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リポジトリにあります。