序章

GraphQL は、 REST API に比べてさまざまな利点があるため、フロントエンド開発の点で人気を博しました。 ただし、独自の GraphQL サーバーのセットアップは、エラーが発生しやすく、複雑です。 このため、 Prisma などのマネージドサービスがGraphQLサーバーを管理するようになり、アプリの開発に集中できるようになりました。

このチュートリアルでは、ReactPrismaを使用してGraphQLを管理する完全に機能するレシピアプリを構築します。

前提条件

  • JavascriptとReactの中間知識
  • GraphQLの基礎
  • Dockerの基礎

ステップ1—依存関係のインストール

次のコマンドを実行して、PrismaCLIクライアントをグローバルにインストールします。

  1. npm install -g prisma

create-react-appを使用してReactアプリをブートストラップするので、次のコマンドを実行してグローバルにインストールします。

  1. npm install -g create-react-app

Prismaをローカルで使用するには、マシンにDockerがインストールされている必要があります。 Dockerをまだお持ちでない場合は、 Docker CommunityEditionをダウンロードできます。

ステップ2—Prismaの設定

Prisma CLIを使用するには、Prismaアカウントが必要です。 Prisma Webサイトでアカウントを作成し、次のコマンドを実行してPrismaCLIにログインできます。

  1. prisma login

必要な依存関係がすべて揃ったので、プロジェクト用のフォルダーを作成し、次のコマンドを実行してフォルダーに移動します。

  1. mkdir recipe-app-prisma-react
  2. cd recipe-app-prisma-react

次に、Prismaサーバーをフォルダーで初期化します。

  1. prisma init

プロンプトが表示され、prismaサーバーのセットアップに使用する方法に関するいくつかのオプションが示されます。 今のところサーバーをローカルで操作し、後でデプロイします。 選ぶ Create new database PrismaにDockerを使用してローカルにデータベースを作成させる。

次に、データベースを選択するためのプロンプトが表示されます。 このチュートリアルではPostgresを使用するので、 PostgreSQL:

次に、生成されたプリズムクライアントのプログラミング言語を選択する必要があります。 選ぶ Prisma Javascript Client:

選択したオプションに基づいて、Prismaによって次のファイルが生成されます。

ステップ3—Prismaの展開

Prismaサーバーがセットアップされたので、dockerが実行されていることを確認します。 次に、次のコマンドを実行してサーバーを起動します。

  1. docker-compose up -d

Docker compose は、複数のコンテナーを単一のサービスとして実行するために使用されます。 前のコマンドは、PrismaサーバーとPostgresデータベースを起動します。 に向かいます 127.0.0.1:4466 ブラウザでPrismaプレイグラウンドを表示します。

サーバーを停止する場合は、 docker-compose stop.

次に、 datamodel.prisma ファイルを作成し、デモコンテンツを次のように置き換えます。

type Recipe {
  id: ID! @unique
  createdAt: DateTime!
  updatedAt: DateTime!
  title: String! @unique
  ingredients: String!
  directions: String!
  published: Boolean! @default(value: "false")
}

次に、次のコマンドを実行してデモサーバーにデプロイします。

  1. prisma deploy

作成されたモデルとPrismaエンドポイントを示す応答が次のように表示されます。

デプロイされたサーバーを表示するには、Prismaダッシュボードを開きます。 https://app.prisma.io/ サービスに移動します。 ダッシュボードに次のように表示されます。

ローカルサーバーに展開するには、 prisma.yml ファイルを作成し、エンドポイントを次のように変更します http://localhost:4466、次に実行します prisma deploy

ステップ4—Reactアプリのセットアップ

Prismaサーバーの準備ができたので、PrismaGraphQLエンドポイントを使用するようにReactアプリを設定できます。

プロジェクトフォルダーで、次のコマンドを実行して、を使用してクライアントアプリをブートストラップします。 create-react-app:

  1. create-react-app client

GraphQLを使用するには、いくつかの依存関係が必要です。 クライアントフォルダに移動し、次のコマンドを実行してインストールします。

  1. cd client
  2. npm install apollo-boost react-apollo graphql-tag graphql --save

UIには、 AntDesignを使用します。

  1. npm install antd --save

フォルダ構造:

アプリのフォルダ構造は次のようになります。

src
├── components
│   ├── App.js
│   ├── App.test.js
│   ├── RecipeCard
│   │   ├── RecipeCard.js
│   │   └── index.js
│   └── modals
│       ├── AddRecipeModal.js
│       └── ViewRecipeModal.js
├── containers
│   └── AllRecipesContainer
│       ├── AllRecipesContainer.js
│       └── index.js
├── graphql
│   ├── mutations
│   │   ├── AddNewRecipe.js
│   │   └── UpdateRecipe.js
│   └── queries
│       ├── GetAllPublishedRecipes.js
│       └── GetSingleRecipe.js
├── index.js
├── serviceWorker.js
└── styles
    └── index.css

ステップ5—コードを書く

Index.js

ここでは、apollo構成を行います。 これは、アプリのメインエントリファイルになります。

import React from 'react';
import ReactDOM from 'react-dom';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';

import App from './components/App';

// Pass your prisma endpoint to uri
const client = new ApolloClient({
  uri: 'https://eu1.prisma.sh/XXXXXX'
});

ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

GetAllPublishedRecipes.js

すべてのレシピを取得するためのクエリ:

import { gql } from 'apollo-boost';

export default gql`query GetAllPublishedRecipes {
    recipes(where: { published: true }) {
      id
      createdAt
      title
      ingredients
      directions
      published
    }
  }`;

GetSingleRecipe.js

レシピIDでレシピをフェッチするためのクエリ:

import { gql } from 'apollo-boost';

export default gql`query GetSingleRecipe($recipeId: ID!) {
    recipe(where: { id: $recipeId }) {
      id
      createdAt
      title
      directions
      ingredients
      published
    }
  }`;

AddNewRecipe.js

新しいレシピを作成するための突然変異:

import { gql } from 'apollo-boost';

export default gql`mutation AddRecipe(
    $directions: String!
    $title: String!
    $ingredients: String!
    $published: Boolean
  ) {
    createRecipe(
      data: {
        directions: $directions
        title: $title
        ingredients: $ingredients
        published: $published
      }
    ) {
      id
    }
  }`;

UpdateRecipe.js

レシピを更新するための突然変異:

import { gql } from 'apollo-boost';

export default gql`mutation UpdateRecipe(
    $id: ID!
    $directions: String!
    $title: String!
    $ingredients: String!
    $published: Boolean
  ) {
    updateRecipe(
      where: { id: $id }
      data: {
        directions: $directions
        title: $title
        ingredients: $ingredients
        published: $published
      }
    ) {
      id
    }
  }`;

AllRecipesContainer.js

これは、 CRUD 操作は基づいています。 ファイルは非常に大きいため、重要な部分のみを含めました。 残りのコードはGitHubで表示できます。

クエリとミューテーションを使用するには、それらをインポートしてから、 react-apollo's graphql を使用すると、 higher-order component これにより、アプリにあるデータに基づいてクエリを実行し、事後対応的に更新できます。

公開されているすべてのレシピを取得して表示する方法の例を次に示します。

import React, { Component } from 'react';
import { graphql } from 'react-apollo';

import { Card, Col, Row, Empty, Spin } from 'antd';

// queries
import GetAllPublishedRecipes from '../../graphql/queries/GetAllPublishedRecipes';

class AllRecipesContainer extends Component {
  render() {
    const { loading, recipes } = this.props.data;

    return (
      <div>
        {loading ? (
          <div className="spin-container">
            <Spin />
          </div>
        ) : recipes.length > 0 ? (
          <Row gutter={16}>
            {recipes.map(recipe => (
              <Col span={6} key={recipe.id}>
                <RecipeCard
                  title={recipe.title}
                  content={
                    <Fragment>
                      <Card
                        type="inner"
                        title="Ingredients"
                        style={{ marginBottom: '15px' }}
                      >
                        {`${recipe.ingredients.substring(0, 50)}.....`}
                      </Card>
                      <Card type="inner" title="Directions">
                        {`${recipe.directions.substring(0, 50)}.....`}
                      </Card>
                    </Fragment>
                  }
                  handleOnClick={this._handleOnClick}
                  handleOnEdit={this._handleOnEdit}
                  handleOnDelete={this._handleOnDelete}
                  {...recipe}
                />
              </Col>
            ))}
          </Row>
        ) : (
          <Empty />
        )}
      </div>
    );
  }
}

graphql(GetAllPublishedRecipes)(AllRecipesContainer);

結果のビューは次のようになります。

注:ファイルサイズのため、コンポーネントのスタイリングは含まれません。 このコードは、GitHubリポジトリで入手できます。

コンポーネントには複数のエンハンサーが必要なため、composeを使用して、コンポーネントに必要なすべてのエンハンサーを組み込みます。

import React, { Component } from 'react';
import { graphql, compose, withApollo } from 'react-apollo';

// queries
import GetAllPublishedRecipes from '../../graphql/queries/GetAllPublishedRecipes';
import GetSingleRecipe from '../../graphql/queries/GetSingleRecipe';

// mutations
import UpdateRecipe from '../../graphql/mutations/UpdateRecipe';
import AddNewRecipe from '../../graphql/mutations/AddNewRecipe';

// other imports

class GetAllPublishedRecipes extends Component {
    // class logic
}

export default compose(
  graphql(UpdateRecipe, { name: 'updateRecipeMutation' }),
  graphql(AddNewRecipe, { name: 'addNewRecipeMutation' }),
  graphql(GetAllPublishedRecipes)
)(withApollo(AllRecipesContainer));

また、 withApollo エンハンサー、あなたの直接アクセスを提供します ApolloClient 実例。 レシピのデータをフェッチするために1回限りのクエリを実行する必要があるため、これは便利です。

レシピの作成

次のフォームからデータをキャプチャした後:

次に、以下を実行します handleSubmit を実行するコールバック addNewRecipeMutation 突然変異:

class GetAllPublishedRecipes extends Component {
  //other logic
   _handleSubmit = event => {
    this.props
      .addNewRecipeMutation({
        variables: {
          directions,
          title,
          ingredients,
          published
        },
        refetchQueries: [
          {
            query: GetAllPublishedRecipes
          }
        ]
      })
      .then(res => {
        if (res.data.createRecipe.id) {
          this.setState(
            (prevState, nextProps) => ({
              addModalOpen: false
            }),
            () =>
              this.setState(
                (prevState, nextProps) => ({
                  notification: {
                    notificationOpen: true,
                    type: 'success',
                    message: `recipe ${title} added successfully`,
                    title: 'Success'
                  }
                }),
                () => this._handleResetState()
              )
          );
        }
      })
      .catch(e => {
        this.setState((prevState, nextProps) => ({
          notification: {
            ...prevState.notification,
            notificationOpen: true,
            type: 'error',
            message: e.message,
            title: 'Error Occured'
          }
        }));
      });
  };
};

レシピの編集

レシピを編集するために、新しいレシピの作成に使用したフォームを再利用して、レシピデータを渡します。 ユーザーが編集アイコンをクリックすると、次のようにデータが事前に入力されたフォームがポップアップ表示されます。

次に、別の handleSubmit 次のように更新ミューテーションを実行するハンドラー:

class GetAllPublishedRecipes extends Component {
  // other logic
  _updateRecipe = ({
    id,
    directions,
    ingredients,
    title,
    published,
    action
  }) => {
    this.props
      .updateRecipeMutation({
        variables: {
          id,
          directions,
          title,
          ingredients,
          published: false
        },
        refetchQueries: [
          {
            query: GetAllPublishedRecipes
          }
        ]
      })
      .then(res => {
        if (res.data.updateRecipe.id) {
          this.setState(
            (prevState, nextProps) => ({
              isEditing: false
            }),
            () =>
              this.setState(
                (prevState, nextProps) => ({
                  notification: {
                    notificationOpen: true,
                    type: 'success',
                    message: `recipe ${title} ${action} successfully`,
                    title: 'Success'
                  }
                }),
                () => this._handleResetState()
              )
          );
        }
      })
      .catch(e => {
        this.setState((prevState, nextProps) => ({
          notification: {
            ...prevState.notification,
            notificationOpen: true,
            type: 'error',
            message: e.message,
            title: 'Error Occured'
          }
        }));
      });
  };
}

レシピを削除する

削除機能については、 soft-delete 削除されたレシピで、これは私たちが変更することを意味します published 記事をフェッチするときに、取得するだけであることを確認するためにフィルタリングするため、falseに属性を設定します published 記事。

次の例に示すように、以前と同じ関数を使用し、publicedをfalseとして渡します。

class GetAllPublishedRecipes extends Component {
   // other logic 
   _handleOnDelete = ({ id, directions, ingredients, title }) => {
    // user confirmed delete prompt 
    this._updateRecipe({
      id,
      directions,
      ingredients,
      title,
      published: false, // soft delete the recipe
      action: 'deleted'
    });
  };
};

結論:

このチュートリアルでは、Prismaを使用してGraphQLサーバーを管理し、ReactとGraphQLを使用してレシピアプリを構築しました。 Prismaは、ビジネスロジックの実装に集中できる信頼性の高いサービスです。

このコードには、GitHubからアクセスできます。