如何使用React、Prisma和GraphQL构建食谱应用程序
介绍
GraphQL 在前端开发方面受到欢迎,因为它比 REST APIs 提供了各种优势。 但是,设置自己的 GraphQL 服务器既容易出错又复杂。 因此,已经使用 Prisma 等托管服务来管理您的 GraphQL 服务器,让您可以专注于应用程序的开发。
在本教程中,我们将使用 React 和 Prisma 构建一个功能齐全的食谱应用程序来管理 GraphQL。
先决条件
- Javascript和React的中级知识
- GraphQL 基础知识
- Docker 基础知识
第 1 步 — 安装依赖项
通过运行以下命令全局安装 Prisma CLI 客户端:
npm install -g prisma
我们将使用 create-react-app 来引导我们的 React 应用程序,因此运行以下命令以全局安装它:
npm install -g create-react-app
要在本地使用 Prisma,您需要在您的机器上安装 Docker。 如果您还没有 Docker,可以下载 Docker 社区版。
第 2 步 — 设置 Prisma
要使用 Prisma CLI,您需要有一个 Prisma 帐户。 您可以在 Prisma 网站 创建一个帐户 ,然后通过运行以下命令登录到 Prisma CLI:
prisma login
现在我们有了所有必需的依赖项,为项目创建一个文件夹并通过运行以下命令导航到该文件夹:
mkdir recipe-app-prisma-react cd recipe-app-prisma-react
然后在文件夹中初始化您的 Prisma 服务器:
prisma init
将出现一个提示,其中包含一些选项,说明您要使用哪种方法来设置您的 prisma 服务器。 我们现在将在本地使用服务器,然后再部署它。 选择 Create new database
让 Prisma 使用 Docker 在本地创建数据库。
接下来,您将收到选择数据库的提示。 对于本教程,我们将使用 Postgres,因此选择 PostgreSQL
:
接下来我们必须为我们生成的 prisma 客户端选择一种编程语言。 选择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 服务器已经准备就绪,我们可以设置我们的 React 应用程序来使用 Prisma GraphQL 端点。
在项目文件夹中,运行以下命令以使用 create-react-app
引导我们的客户端应用程序:
create-react-app client
要使用 GraphQL,我们需要一些依赖项。 导航到客户端文件夹并运行以下命令来安装它们:
cd client npm install apollo-boost react-apollo graphql-tag graphql --save
对于 UI,我们将使用 Ant Design:
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 步 — 编写代码
索引.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 } }`;
更新配方.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
实例的直接访问。 这将很有用,因为我们需要执行一次性查询来获取配方的数据。
创建配方
从以下表单捕获数据后:
然后我们执行以下 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
文章。
我们将使用与之前相同的函数,并将published作为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' }); }; };
结论:
在本教程中,您使用 React 和 GraphQL 构建了一个食谱应用程序,并使用 Prisma 来管理您的 GraphQL 服务器。 Prisma 是一项可靠的服务,可让您专注于实现业务逻辑。
您可以在 GitHub 访问代码。