در یکی دو سال اخیر، GraphQL با توجه به برتریهای متنوعی که در زمینه REST دارد، توانسته در صحنه توسعهدهی frontend بسیار پیش برود.
گرچه، راهاندازی یک سرور GraphQL مختص خود چالش برانگیز میباشد. این کار هم پر خطا، و هم پیچیده است. این مسئله توسط سرویسهای مدیریتی مانند Prisma که کارهای سخت سرور GraphQL شما را انجام میدهند، آسانتر شده است و در نتیجه تمرکز بر روی توسعهدهی برنامه خود، برای شما سادهتر است.
مقالات مرتبط: GraphQL - نکات خوب و بد
در این آموزش، ما یک برنامه مربوط به دستور پخت غذا با استفاده از Prisma و React خواهیم ساخت.
جدوا محتوا:
- پیشنیازها
- نصب
- راهاندازی Prisma
- گسترش
- راهاندازی برنامه React
- کد
- نتیجه گیری
پیشنیازها
- دانش متوسط در زمینه JavaScript و React.
- اساس GraphQL.
- اساس Docker. اگر هیچ تجربه پیشینی در آن ندارید، نترسید. فقط دستورات موجود را کپی و پیست کنید.
نصب
ما باید کلاینت Prisma CLI را به صورت global و با اجرای این دستور نصب کنیم:
npm install -g prisma
ما از create-react-app برای bootstrap کردن برنامه React خود استفاده خواهیم کرد. این دستور را اجرا کرده، تا آن را به صورت global نصب کنید:
npm install -g create-react-app
برای این که از Prisma به صورت محلی استفاده کنید، باید Docker را بر روی دستگاه خود نصب داشته باشید. اگر هنوز Docker را ندارید، میتوانید نسخه Community آن را در این لینک دانلود کنید.
راهاندازی Prisma
برای استفاده از Prisma CLI، شما باید یک حساب کاربری Prisma داشته باشید. شما میتوانید در این لینک یک حساب Prisma بسازید، و سپس با استفاده از این دستور به Prisma CLI وارد شوید:
prisma login
حال که ما تمام dependencyهای مورد نیاز برای شروع پروژه خود را به صورت نصب شده داریم، به ترمینال بروید، یک پوشه جدید برای پروژه بسازید و با استفاده از این دستورات به آن پوشه جهتیابی کنید:
mkdir recipe-app-prisma-react
cd recipe-app-prisma-react
سپس ما سرور Prisma خود را در پوشه مورد نظر راهاندازی خواهیم کرد:
prisma init
یک درخواست مجوز به همراه چند گزینه مربوط به متدی که میخواهید برای راهاندازی سرور Prisma خود استفاده کنید، ظاهر خواهد شد. ما در حال حاضر به صورت محلی با سرور کار خواهیم کرد و سپس بعدا آن را گسترش خواهیم داد. Create new database را انتخاب کنید، تا Prisma یک دیتابیس را به صورت محلی با استفاده از Docker بسازد.
سپس، از شما درخواست خواهد شد که یک دیتابیس را انتخاب کنید. برای این آموزش، ما از PostgreSQL استفاده خواهیم کرد:
سپس باید یک زبان برای کلاینت Prisma تولید شده انتخاب کنید. Prisma JavaScript Client را انتخاب کنید (ما از type checkerها استفاده نخواهیم کرد، تا این آموزش را ساده نگه داریم):
حال فایلهای زیر باید بر پایه گزینههای انتخاب شده توسط Prisma ساخته شده باشند:
گسترش
حال که سرور Prisma ما راهاندازی شده است، مطمئن شوید که Docker در حال اجراست و این دستور را برای شروع سرور اجرا کنید:
docker-compose up -d
Docker compose برای اجرای چند محفظه (Container) بر روی یک سرویس استفاده میشود. دستور بالا سرور 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 را اجرا کنید.
راهاندازی برنامه React
حال که سرور Prisma ما آماده است، میتوانیم برنامه React خود را راهاندازی کنیم، تا اندپوینت GraphQL را در بر گیرد.
در پوشه پروژه، این دستور را اجرا کنید تا با استفاده از create-react-app برنامه client خود را bootstrap کنیم:
create-react-app client
برای کار با GraphQL، ما به چند dependency نیاز خواهیم داشت. به پوشه client بروید و این دستور را برای نصب آنها اجرا کنید:
cd client
npm install apollo-boost react-apollo graphql-tag graphql --save
برای رابط کاربری، ما از 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
کد
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 در آن قرار دارند. این فایل بسیار عظیم است. من بخشهای نامربوط را حذف کردهام، تا برای بخشهای حیاتی جا باز کنم. شما میتوانید باقی کد را در این لینک مشاهده نمایید.
ما در جهت استفاده از کوئریها و جهشهای خود، باید آنها را وارد کرده، و سپس از دستور graphql در react-apollo استفاده کنیم، که ما را قادر میسازد تا یک کامپوننت سطح بالا (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);
نتیجه نهایی چنین ظاهری دارد:
نکته: با توجه به سایز فایل، استایلبندی کامپوننتها در اینجا شامل نخواهد شد. گرچه، کد مربوطه در مخزن گیتهاب موجود است.
از آنجایی که ما بیش از یک تقویت کننده را در کامپوننت خود لازم کردهایم، از compose استفاده خواهیم کرد تا بتوانیم تمام تقویت کنندههای مورد نیاز بری کامپوننت را ترکیب کنیم:
import React, { Component } from 'react';
import { graphql, compose, withApollo } from 'react-apollo';
// کوئریها
import GetAllPublishedRecipes from '../../graphql/queries/GetAllPublishedRecipes';
import GetSingleRecipe from '../../graphql/queries/GetSingleRecipe';
// جهشها
import UpdateRecipe from '../../graphql/mutations/UpdateRecipe';
import AddNewRecipe from '../../graphql/mutations/AddNewRecipe';
// ورودهای دیگر
class GetAllPublishedRecipes extends Component {
// منطق کلاس
}
export default compose(
graphql(UpdateRecipe, { name: 'updateRecipeMutation' }),
graphql(AddNewRecipe, { name: 'addNewRecipeMutation' }),
graphql(GetAllPublishedRecipes)
)(withApollo(AllRecipesContainer));
ما همچنین تقویت کننده withApollo که دسترسی مستقیم به نمونه ApolloClient ما را فراهم میکند را لازم میکنیم. از آنجایی که ما باید کوئریهای یک طرفه را برای گرفتن دادههای یک دستور پخت اجرا کنیم، این مورد کاربردی خواهد بود.
ساخت یک دستور پخت:
بعد از گرفتن دادهها از فرم زیر:
ما تابع callback با نام handleSubmit را اجرا خواهیم کرد، که جهش addNewRecipeMutation را اجرا میکند:
class GetAllPublishedRecipes extends Component {
//باقی منطق
_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 {
// باقی منطق
_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 را طبق مثال زیر، به عنوان false منتقل میکنیم:
class GetAllPublishedRecipes extends Component {
// باقی منطق
_handleOnDelete = ({ id, directions, ingredients, title }) => {
// وقتی که کاربر مجوز حذف را تایید کرد
this._updateRecipe({
id,
directions,
ingredients,
title,
published: false, // soft delete the recipe
action: 'deleted'
});
};
};
شما میتوانید به کد مربوطه در این لینک دسترسی داشته، و همچنین برنامه دمو را در این لینک امتحان کنید.
نتیجه گیری
Prisma یک سرویس قابل اطمینان است که زندگی را برای شما به عنوان یک توسعه دهنده سادهتر میکند. شما میتوانید بر روی پیادهسازی منطق کسب و کار خود تمرکز کنید و کارهای سخت مربوط به سرور GraphQL خود را با استفاده از آن انجام دهید.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید