React、Prisma、GraphQLを使用してレシピアプリを構築する方法
序章
GraphQL は、 REST API に比べてさまざまな利点があるため、フロントエンド開発の点で人気を博しました。 ただし、独自の GraphQL サーバーのセットアップは、エラーが発生しやすく、複雑です。 このため、 Prisma などのマネージドサービスがGraphQLサーバーを管理するようになり、アプリの開発に集中できるようになりました。
このチュートリアルでは、ReactとPrismaを使用してGraphQLを管理する完全に機能するレシピアプリを構築します。
前提条件
- JavascriptとReactの中間知識
- GraphQLの基礎
- Dockerの基礎
ステップ1—依存関係のインストール
次のコマンドを実行して、PrismaCLIクライアントをグローバルにインストールします。
npm install -g prisma
create-react-appを使用してReactアプリをブートストラップするので、次のコマンドを実行してグローバルにインストールします。
npm install -g create-react-app
Prismaをローカルで使用するには、マシンにDockerがインストールされている必要があります。 Dockerをまだお持ちでない場合は、 Docker CommunityEditionをダウンロードできます。
ステップ2—Prismaの設定
Prisma CLIを使用するには、Prismaアカウントが必要です。 Prisma Webサイトでアカウントを作成し、次のコマンドを実行してPrismaCLIにログインできます。
prisma login
必要な依存関係がすべて揃ったので、プロジェクト用のフォルダーを作成し、次のコマンドを実行してフォルダーに移動します。
mkdir recipe-app-prisma-react cd recipe-app-prisma-react
次に、Prismaサーバーをフォルダーで初期化します。
prisma init
プロンプトが表示され、prismaサーバーのセットアップに使用する方法に関するいくつかのオプションが示されます。 今のところサーバーをローカルで操作し、後でデプロイします。 Create new database
を選択して、PrismaにDockerを使用してローカルにデータベースを作成させます。
次に、データベースを選択するためのプロンプトが表示されます。 このチュートリアルではPostgresを使用するため、PostgreSQL
を選択します。
次に、生成されたプリズムクライアントのプログラミング言語を選択する必要があります。 Prisma Javascript Client
を選択します。
選択したオプションに基づいて、Prismaによって次のファイルが生成されます。
ステップ3—Prismaの展開
Prismaサーバーがセットアップされたので、dockerが実行されていることを確認します。 次に、次のコマンドを実行してサーバーを起動します。
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") }
次に、次のコマンドを実行してデモサーバーにデプロイします。
prisma deploy
作成されたモデルとPrismaエンドポイントを示す応答が次のように表示されます。
デプロイされたサーバーを表示するには、https://app.prisma.io/
でPrismaダッシュボードを開き、サービスに移動します。 ダッシュボードに次のように表示されます。
ローカルサーバーにデプロイするには、prisma.yml
ファイルを開き、エンドポイントをhttp://localhost:4466
に変更してから、prisma deploy
を実行します。
ステップ4—Reactアプリのセットアップ
Prismaサーバーの準備ができたので、PrismaGraphQLエンドポイントを使用するようにReactアプリを設定できます。
プロジェクトフォルダーで、次のコマンドを実行して、create-react-app
を使用してクライアントアプリをブートストラップします。
create-react-app client
GraphQLを使用するには、いくつかの依存関係が必要です。 クライアントフォルダに移動し、次のコマンドを実行してインストールします。
cd client npm install apollo-boost react-apollo graphql-tag graphql --save
UIには、 AntDesignを使用します。
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));
また、ApolloClient
インスタンスへの直接アクセスを提供するwithApollo
エンハンサーも必要です。 レシピのデータをフェッチするために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からアクセスできます。