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エンドポイントを示す応答が次のように表示されます。
デプロイされたサーバーを表示するには、Prismaダッシュボードを開きます。 https://app.prisma.io/
サービスに移動します。 ダッシュボードに次のように表示されます。
ローカルサーバーに展開するには、 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));
また、 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からアクセスできます。