Cách xây dựng ứng dụng Recipe bằng React, Prisma và GraphQL
GraphQL đã trở nên phổ biến về mặt phát triển giao diện user do những lợi thế khác nhau mà nó mang lại so với các API REST . Tuy nhiên, việc cài đặt server GraphQL của bạn vừa dễ xảy ra lỗi vừa phức tạp. Do đó, các dịch vụ được quản lý như Prisma đã được thực hiện để quản lý server GraphQL của bạn, cho phép bạn tập trung vào việc phát triển ứng dụng của bạn .Trong hướng dẫn này, ta sẽ xây dựng một ứng dụng công thức đầy đủ chức năng bằng cách sử dụng React và Prisma để quản lý GraphQL.
Yêu cầu
- Kiến thức trung cấp về Javascript và React
- Các nguyên tắc cơ bản về GraphQL
- Các nguyên tắc cơ bản của Docker
Bước 1 - Cài đặt phụ thuộc
Cài đặt client Prisma CLI trên phạm vi global bằng cách chạy lệnh sau:
- npm install -g prisma
Ta sẽ sử dụng create-react-app để khởi động ứng dụng React của bạn , vì vậy hãy chạy lệnh sau để cài đặt nó trên phạm vi global :
- npm install -g create-react-app
Để sử dụng Prisma local , bạn cần cài đặt Docker trên máy của bạn . Nếu bạn chưa có Docker, bạn có thể download Docker Community Edition .
Bước 2 - Cài đặt Prisma
Để sử dụng Prisma CLI, bạn cần phải có account Prisma. Bạn có thể tạo một account tại trang web Prisma , sau đó đăng nhập vào Prisma CLI bằng cách chạy lệnh sau:
- prisma login
Bây giờ ta đã có tất cả các phụ thuộc cần thiết, hãy tạo một folder cho dự án và chuyển vào folder bằng cách chạy các lệnh sau:
- mkdir recipe-app-prisma-react
- cd recipe-app-prisma-react
Sau đó khởi tạo server Prisma của bạn trong folder :
- prisma init
Một dấu nhắc sẽ xuất hiện với một vài tùy chọn về phương pháp bạn muốn sử dụng để cài đặt server lăng trụ của bạn . Ta sẽ làm việc với server local ngay bây giờ và sau đó triển khai nó sau. Chọn Create new database
để Prisma tạo database local bằng Docker .
Tiếp theo, bạn sẽ nhận được dấu nhắc chọn database . Đối với hướng dẫn này, ta sẽ sử dụng Postgres, vì vậy hãy chọn PostgreSQL
:
Tiếp theo, ta phải chọn một ngôn ngữ lập trình cho ứng dụng client lăng trụ đã tạo của ta . Chọn Prisma Javascript Client
:
Bạn sẽ có các file sau do Prisma tạo dựa trên các tùy chọn đã chọn:
Bước 3 - Triển khai Prisma
Bây giờ ta đã cài đặt server Prisma của bạn , hãy đảm bảo docker đang chạy. Sau đó, chạy lệnh sau để khởi động server :
- docker-compose up -d
Docker composer được sử dụng để chạy nhiều containers như một dịch vụ duy nhất. Lệnh trước đó sẽ khởi động server Prisma của ta và database Postgres. 127.0.0.1:4466
trong trình duyệt của bạn để xem playground Prisma.
Nếu bạn muốn dừng server của bạn , hãy chạy docker-compose stop
.
Tiếp theo, mở file datamodel.prisma
của bạn và thay thế nội dung demo bằng nội dung sau:
type Recipe { id: ID! @unique createdAt: DateTime! updatedAt: DateTime! title: String! @unique ingredients: String! directions: String! published: Boolean! @default(value: "false") }
Sau đó, chạy lệnh sau để triển khai tới server demo:
- prisma deploy
Bạn sẽ nhận được phản hồi hiển thị các mô hình đã tạo và điểm cuối Prisma của bạn như sau:
Để xem server đã triển khai, hãy mở console Prisma tại https://app.prisma.io/
và chuyển đến các dịch vụ. Bạn sẽ có những thông tin sau hiển thị trong trang tổng quan của bạn :
Để triển khai tới server local của bạn, hãy mở file prisma.yml
và thay đổi điểm cuối thành http://localhost:4466
, sau đó chạy prisma deploy
prisma.yml
Bước 4 - Cài đặt ứng dụng React
Bây giờ server Prisma của ta đã sẵn sàng, ta có thể cài đặt ứng dụng React của bạn để sử dụng điểm cuối Prisma GraphQL.
Trong folder dự án, hãy chạy lệnh sau để khởi động ứng dụng client của ta bằng cách sử dụng create-react-app
:
- create-react-app client
Để làm việc với GraphQL, ta yêu cầu một số phụ thuộc. Điều hướng vào folder khách và chạy các lệnh sau để cài đặt chúng:
- cd client
- npm install apollo-boost react-apollo graphql-tag graphql --save
Đối với giao diện user , ta sẽ sử dụng Ant Design :
- npm install antd --save
Cấu trúc folder :
Cấu trúc folder ứng dụng của ta sẽ như sau:
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
Bước 5 - Viết mã
Index.js
Ở đây ta sẽ thực hiện cấu hình apollo. Đây sẽ là file mục nhập chính cho ứng dụng của ta :
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
Truy vấn tìm nạp tất cả các công thức nấu ăn:
import { gql } from 'apollo-boost'; export default gql`query GetAllPublishedRecipes { recipes(where: { published: true }) { id createdAt title ingredients directions published } }`;
GetSingleRecipe.js
Truy vấn để tìm nạp một công thức theo id công thức:
import { gql } from 'apollo-boost'; export default gql`query GetSingleRecipe($recipeId: ID!) { recipe(where: { id: $recipeId }) { id createdAt title directions ingredients published } }`;
AddNewRecipe.js
Đột biến để tạo ra một công thức mới:
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
Đột biến để cập nhật một công thức:
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
Đây là nơi dựa trên logic của ta cho CRUD
hoạt động CRUD
. Tệp khá lớn, vì vậy ta chỉ bao gồm những phần quan trọng. Bạn có thể xem phần còn lại của mã trên GitHub .
Để sử dụng các truy vấn và đột biến của ta , ta cần nhập chúng và sau đó sử dụng graphql react-apollo's
react react-apollo's
cho phép ta tạo higher-order component
có thể thực hiện các truy vấn và cập nhật một cách phản ứng dựa trên dữ liệu ta có trong ứng dụng của bạn .
Dưới đây là một ví dụ về cách ta có thể tìm nạp và hiển thị tất cả các công thức đã xuất bản:
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);
Chế độ xem kết quả sẽ như sau:
Lưu ý: Việc tạo kiểu cho các thành phần sẽ không được bao gồm do kích thước file . Mã có sẵn trong repo GitHub .
Vì ta yêu cầu nhiều hơn một chất tăng cường trong thành phần của bạn , ta sẽ sử dụng chế phẩm để kết hợp tất cả các chất tăng cường cần thiết cho thành phần:
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));
Ta cũng yêu cầu trình tăng cường withApollo
, cung cấp quyền truy cập trực tiếp vào ApolloClient
của bạn. Điều này sẽ hữu ích, vì ta cần thực hiện các truy vấn một lần để tìm nạp dữ liệu cho một công thức.
Tạo công thức
Sau khi thu thập dữ liệu từ biểu mẫu sau:
Sau đó, ta thực hiện lệnh gọi lại handleSubmit
sau, chạy đột biến 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' } })); }); }; };
Chỉnh sửa công thức
Để chỉnh sửa công thức, ta sử dụng lại biểu mẫu đã dùng để tạo công thức mới và sau đó chuyển dữ liệu công thức. Khi user nhấp vào biểu tượng chỉnh sửa, biểu mẫu sẽ bật lên với dữ liệu được điền sẵn như sau:
Sau đó, ta chạy một trình xử lý handleSubmit
khác để chạy đột biến cập nhật như sau:
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' } })); }); }; }
Xóa công thức
Đối với chức năng xóa, ta sẽ thực hiện soft-delete
đối với công thức đã xóa, nghĩa là ta sẽ thay đổi thuộc tính đã published
thành sai vì khi tìm nạp các bài báo ta lọc đảm bảo rằng ta chỉ nhận được các bài báo đã published
.
Ta sẽ sử dụng chức năng tương tự như trước và chuyển vào được xuất bản là false, như trong ví dụ sau:
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' }); }; };
Kết luận :
Trong hướng dẫn này, bạn đã xây dựng một ứng dụng công thức với React và GraphQL, sử dụng Prisma để quản lý server GraphQL của bạn . Prisma là một dịch vụ tin cậy cho phép bạn tập trung vào việc thực hiện logic kinh doanh của bạn.
Bạn có thể truy cập mã tại GitHub .
Các tin liên quan