prisma orm_使用Prisma和React构建RECIPE应用

prisma orm

In the last couple of years, GraphQL has taken on the scene in terms of frontend development due to the various advatanges it offers over REST.

在过去的几年中, GraphQL已经在前端开发方面的现场,由于各种advatanges它提供了REST

However, setting up your own GraphQL server is challenging, it's both error-prone and complicated. This has been made easier due to managed services such as Prisma which does the heavy lifting in your GraphQL server making it easier for you to instead focus on the development of you app.

但是,设置自己的GraphQL服务器具有挑战性,因为它容易出错而且很复杂。 由于诸如Prisma之类的托管服务,这使GraphQL服务器的工作变得更加轻松,这使其变得更加容易,从而使您更轻松地专注于应用程序的开发。

In this tutorial, we will be building a fully functional Recipe app using Prisma and React.

在本教程中,我们将使用PrismaReact构建功能齐全的Recipe应用程序。

先决条件 ( Prerequisites )

  • Intermediate knowledge of Javascript and React

    Java和React的中级知识
  • GraphQL fundamentals

    GraphQL基础
  • Docker fundamentals - don't panic if you have no prior experience, just copy paste the commands and hopefully all goes well :)

    Docker基础-如果您没有任何经验,请不要惊慌,只需复制粘贴命令,希望一切顺利:)

安装 ( Installation )

We need to install the prisma cli client globally by running the following command:

我们需要通过运行以下命令来全局安装prisma cli客户端:

npm install -g prisma

We will be using create-react-app to bootstrap our react app, run the folowing command to install it globally:

我们将使用create-react-app引导我们的react应用程序,运行folowing命令将其全局安装:

npm install -g create-react-app

To use Prisma locally, you need to have Docker installed on your machine. If you don't have Docker yet, you can download the Docker Community Edition here.

要在本地使用Prisma,您需要在计算机上安装Docker 。 如果您还没有Docker,则可以在此处下载Docker Community Edition。

棱镜设置 ( Prisma Setup )

To use the prisma cli, you will need to have a prisma account, you can create an account here then login to prisma cli by running the following command:

要使用prisma cli,您将需要有一个prisma帐户,您可以在此处创建一个帐户然后通过运行以下命令来登录prisma cli:

prisma login

Now that we have all the required dependecies to kickstart our project installed, head over to your terminal and create a folder for the project (somewhere in your code stash) and navigate into the folder by running the following commands:

现在,我们已经具备了启动项目所需的所有条件,然后转到您的终端并为该项目创建一个文件夹(在代码存储区的某个位置),然后通过运行以下命令导航到该文件夹​​:

mkdir recipe-app-prisma-react 
cd recipe-app-prisma-react

Then we initialize our prisma server in the folder:

然后,我们在文件夹中初始化prisma服务器:

prisma init

A prompt will appear with a few options on which method you want to use to setup your prisma server, we will be working with the server locally for now and then deploy it later, choose Create new database to have prisma create a database locally with Docker

将会出现一个提示,其中包含一些选项,供您选择要使用哪种方法来设置您的prisma服务器,我们现在将在本地使用该服务器,然后再进行部署,选择“ Create new database以使prisma通过Docker在本地创建数据库

Next, you'll get a prompt to choose a database, for this tutorial we will be using Postgres so choose PostgreSQL:

接下来,提示您选择数据库,在本教程中,我们将使用Postgres,因此选择PostgreSQL

Next we have to choose a language for out generated prisma client, choose Prisma Javascript Client(sorry flow and typescript fanatics :( won't be using any type checkers so as to keep the tutorial simple )

接下来,我们必须为生成的prisma客户端选择一种语言,选择Prisma Javascript Client (抱歉,流程和打字稿狂热者:(为了简化本教程,将不使用任何类型检查器)

You should have the following files generated by Prisma based on the above selected options:

您应具有Prisma根据上述所选选项生成的以下文件:

部署方式 ( Deployment )

Now that we have our prisma server setup, make sure docker is running and the run the following command to start the server:

既然我们已经完成了棱镜服务器的设置,请确保docker正在运行,并运行以下命令来启动服务器:

docker-compose up -d

Docker compose is used to run multiple containers as a single service, the above command will start our prisma server and the postgres database, head over to 127.0.0.1:4466 to view the prisma playground. Incase you want to kill your server just run docker-compose stop

Docker compose用于将多个容器作为单个服务运行,上述命令将启动我们的prisma服务器和postgres数据库,并转到127.0.0.1:4466 观看prisma游乐场。 如果您想杀死服务器,只需运行docker-compose stop

Open your datamodel.prisma file and replace the demo content with the following:

打开您的datamodel.prisma文件,并将演示内容替换为以下内容:

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

Then run the following command to deploy to a demo server:

然后运行以下命令以部署到演示服务器:

prisma deploy

You should get a response showing the created models and your prisma endpoint as follows:

您应该得到如下响应,其中显示了创建的模型和您的prisma端点,如下所示:

To view the deployed server, open you prisma dashboard at https://app.prisma.io/ and navigate to services, you should have the following showing in your dashboard:

要查看已部署的服务器,请在https://app.prisma.io/上打开prisma仪表板,然后导航至服务,您的仪表板中应显示以下内容:

To deploy to your local server open the prisma.yml file and change the endpoint to http://localhost:4466 then run prisma deploy

要部署到本地服务器,请打开prisma.yml文件,并将端点更改为http://localhost:4466然后运行prisma deploy

React应用程序设置 ( React App Setup )

Now that our prisma server is ready, we can setup our react app to consume the prisma GraphQL endpoint.

现在我们的棱镜服务器已经准备就绪,我们可以设置我们的react应用程序以使用pyramida GraphQL端点。

In the project folder, run the following command to bootstrap our client app using create-react-app

在项目文件夹中,运行以下命令以使用create-react-app引导我们的客户端应用create-react-app

create-react-app client

To work with graphql, we will require a few dependecies, navigate into the client folder and run the following command to install them:

要使用graphql,我们将需要一些依赖项,进入client文件夹并运行以下命令来安装它们:

cd client
npm install apollo-boost react-apollo graphql-tag graphql --save

For the UI we will be using Ant Design:

对于UI,我们将使用Ant Design

npm install antd --save

资料夹结构: (Folder Structure:)

Our app folder structure will be as follows:

我们的应用程序文件夹结构如下:

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

代码 ( The Code )

Index.js (Index.js)

In here is where we do the apollo config, it's the main entry file for our app:

在这里,我们进行阿波罗配置,这是我们应用程序的主要入口文件:

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 (GetAllPublishedRecipes.js)

Query to fetch all recipes:

查询以获取所有配方:

import{ gql } from 'apollo-boost';

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

GetSingleRecipe.js (GetSingleRecipe.js)

Query to fetch a recipe by the recipe id:

通过配方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 (AddNewRecipe.js)

The mutation for creating a new recipe:

创建新配方的变体:

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 (UpdateRecipe.js)

The mutation for updating a recipe:

更新配方的突变:

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 (AllRecipesContainer.js)

This is where our logic for the CRUD operations is based, the file is quite big, I've omitted the irrelavant parts to make space for the crucial bits, you can view the rest of the code here.

这是我们CRUD操作的逻辑基础,文件很大,我省略了无关紧要的部分来为关键位留出空间,您可以在这里查看其余的代码。

In order to use our queries and mutations, we need to import them and then use the react-apollo's graphql that allows us to create a higher-order component that can execute queries and update reactively based on the data we have in our app, here is an example of how we can fetch and display all published recipes:

为了使用我们的查询和变异,我们需要导入它们,然后使用react-apollo's graphql ,它使我们可以创建一个higher-order component ,该higher-order component可以执行查询并根据我们应用程序中的数据进行React式更新,此处是我们如何获取和显示所有已发布食谱的示例:

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);

The resulting view would look as follows:

结果视图如下所示:

NB: Styling for the components will not be included due to file size, the code is however available in the repo

注意:由于文件大小,将不包括组件的样式,但是代码可在存储库中找到

Since we have require a more than one enhancer in our component, we will use compose to be able to incorporate all needed enhancers for the component:

由于我们的组件中需要一个以上的增强器,因此我们将使用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));

We also require the withApollo enhancer which provides direct access to your ApolloClient instance, This will be useful since we need to carry out one-off queries for fetching data for a recipe.

我们还需要withApollo增强器,该增强器可直接访问您的ApolloClient实例。这将很有用,因为我们需要执行一次性查询来获取配方数据。

创建配方: (Creating a recipe:)

After capturing the data from the following form:

从以下形式捕获数据后:

We then execute the following handleSubmit callback which runs the addNewRecipeMutation mutation:

然后,我们执行以下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'
          }
        }));
      });
  };
};

编辑配方: (Editing a recipe:)

In order to edit a recipe, we re-use the form used to create a new recipe and then pass the recipe data, when a user clicks on the edit icon, the form pops up with the data pre-filled as follows:

为了编辑配方,我们重复使用用于创建新配方的表格,然后传递配方数据,当用户单击编辑图标时,该表格将弹出并预先填充数据,如下所示:

We then run a different handleSubmit handler to run the update mutation as follows:

然后,我们运行一个不同的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'
          }
        }));
      });
  };
}

删除配方: (Deleting a recipe:)

As for the delete functionality, we will be doing a soft-delete on the deleted recipe which means we will be basically be changing the published attribute to false since when fetching the articles we filter to make sure we only get published articles.

至于删除功能,我们将对已删除的配方进行soft-delete ,这意味着我们将基本上将已published属性更改为false,因为在获取文章时我们会进行过滤以确保仅获取published文章。

We will use the same function above and pass in published as false, as shown in the example below:

我们将使用上面相同的函数,并以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'
    });
  };
};

You can access the code here and you can also try out the demo app here.

您可以访问代码, 在这里 ,你也可以尝试演示程序在这里

结论: ( Conclusion: )

Prisma is a very highly reliable service that makes life easier for you as a developer, you get to focus on implementing you business logic and let prisma do the heavy lifting for your GraphQL server.

Prisma是一种非常高度可靠的服务,它使您作为开发人员的工作变得更轻松,您可以专注于实现业务逻辑,让prisma为您的GraphQL服务器承担繁重的工作。

翻译自: https://scotch.io/tutorials/building-a-recipe-app-using-prisma-and-react

prisma orm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值