介绍
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 访问代码。