אם פעם חשבתם ש graphql הוא הפתרון הטוב ביותר לקריאות דטה בייס ככל הנראה צדקדם. יותר ויותר שימושים עם graphql ופחות ב REST. לכן חשוב מאוד להכיר! . אז אם אתה מתכנת צד לקוח או צד שרת, שעוד לא מכירים graphql, הנה פוסט שיכניס אתכם לעניינים ברגל ימין.
מה הוא GQL?
בהגדרה הפשוטה ביותר GQL היא שפה מבוססת שאילתות (מונחת JSON) שמתבצעת על ידי קריאה אחת לAPI ומחזירה מידע בהתאם לשאילתה שלכם. לעומת REST שבחלק גדול מהמקרים מבצע הרבה קריאות. מדובר בשפה שמורכבת משאילות . להלן דוגמה:
אם תרצו לבקש מידע מתוך רשימה של סופרים, רק את שם הסופר וכן את שמות הספרים שאותו כותב כתב. אילו קריאות תדרשו לעשות עם REST ?
ככל הנראה לבקש את כל המידע (לינק) של כל הסופרים,קריאה זו תביא איתה את כל המידע על הסופרים השונים ולא רק את השם, וכן תצטרכו לעשות קריאות נוספות עבור כל כותב ולבקש את רשימת הספרים שלו authors/:id/books
עם GQL זה נראה כך:
query { authors { id name books { name } } }
שאליתה זו תביא את המידע המבוקש בקריאה אחת ובלי מידע מיותר. מה שמאפשר גמישות רבה ונוחות.
יתרונות רבים של GQL
- מהיר יותר
- קריאה אחת בלבד
- אפשרות לקבל מידע ספציפי
- הגדרה של schema
- פחות טעויות בבקשת המידע או כתיבה
- קבלת עדכונים ברגע שמידע התעדכן בשרת (באמצעות subscription ולא דרך HTTP )
דוגמא GQL
להלן דוגמה פשוטה וקצרה, דוגמה זו תציג בסיס טוב להיכרות שלכם עם GQL .
נצטרך כמה דברים על מנת לבנות את הדוגמה הזו:
- צד לקוח כלשהו – אשתמש ב React
- צד שרת כלשהו – אשתמש ב nodejs
- מסד נתונים כלשהו – אשתמש ב firebase
נתחיל צד שרת NODEJS ומימוש של GQL
טוב אז נכין צד שרת פשוט עם Nodejs . כל מה שאנחנו צריכים זה קובץ index.js
ולאחר npm init נבצע את ההתקנות הבאות:
npm i express express-graphql graphql npm install nodemon --save-dev
להלן קובץ package.json מי שרוצה לראות או להשתמש (יש פה כמה נוספים של סטנדרטים)
{ "name": "server", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon --exec babel-node src/index.js" }, "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.15.0", "@babel/node": "^7.14.9", "@babel/preset-env": "^7.15.0", "nodemon": "^2.0.12" }, "dependencies": { "express": "^4.17.1", "express-graphql": "^0.12.0", "graphql": "^15.5.1" } }
בהמשך נרים סרבר בסיסי עם express ו GQL:
אחד הדברים היותר נחמדים של לGQL זו סביבה של טסטים מעולה. אפשר לבדוק שאילתות ועוד בקלילות ובממשק UI נעים ואינטואיטיבי. כנסו לקישור שזה עתה יצרתם: localhost:{your_PORT}/graphql . (לא יעבוד לכם כרגע . המשיכו לקרוא)
const express = require('express'); const app = express(); const expressGraphQL = require('express-graphql').graphqlHTTP; app.use('/graphql', expressGraphQL({ graphiql: true })); app.listen(5001, () => console.log('codcodile GQL tut is running'))
מה זה Schema
טוב אז הקוד שלמעלה לא עובד לכם כי GQL מבקש schema. שזה למעשה אינטרפייס שמגדיר את הדטה בצד השרת, שGQL הולך לעבוד איתו. ולכן צריך לבנות לו schema מסודרת לפי דרישות קפדניות. זה מאד נוח להמשך העבודה עם GQL.
להלן מה קובץ אינדקס עם התוספת של schema:
* זהו הקובץ המלא והסופי של הפויקט דמו. אפרט בהמשך על כל דבר ודבר.
const express = require('express'); const expressGraphQL = require('express-graphql').graphqlHTTP; const app = express(); const { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLList, GraphQLInt, GraphQLNonNull } = require('graphql'); const authors = [ { id: 1, name: 'codcodile baby' }, { id: 2, name: 'codcodile daddy' }, { id: 3, name: 'codcodile momi' } ] const books = [ { id: 1, name: ' simply dummy text of the printing and typesetting industry. Lorem Ipsum has been ', authorId: 1 }, { id: 2, name: '1960s with the release of Letraset sheets', authorId: 1 }, { id: 3, name: 'Harry Potter and the Goblet of Fire', authorId: 1 }, { id: 4, name: 'popular belief, Lorem Ipsum is not simply random', authorId: 2 }, { id: 5, name: 'using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content', authorId: 2 }, { id: 6, name: 'sites still in their infancy. Various versions have evolved over the years, sometimes by accident', authorId: 2 }, { id: 7, name: 'Latin words, consectetur, from ', authorId: 3 }, { id: 8, name: 'majority have suffered alteration in some form', authorId: 3 } ]; const BookType = new GraphQLObjectType({ name: 'Book', description: 'This represents a book written by an author', fields: () => ({ id: { type: GraphQLNonNull(GraphQLInt) }, name: { type: GraphQLNonNull(GraphQLString) }, authorId: { type: GraphQLNonNull(GraphQLInt) }, author: { type: AuthorType, resolve: (book) => { return authors.find(author => author.id === book.authorId) } } }) }) const AuthorType = new GraphQLObjectType({ name: 'Author', description: 'This represents a author of a book', fields: () => ({ id: { type: GraphQLNonNull(GraphQLInt) }, name: { type: GraphQLNonNull(GraphQLString) }, books: { type: new GraphQLList(BookType), resolve: (author) => { return books.filter(book => book.authorId === author.id) } } }) }) const RootQueryType = new GraphQLObjectType({ name: 'Query', description: 'Root Query', fields: () => ({ book: { type: BookType, description: 'Current Book', args: { id: { type: GraphQLInt } }, resolve: (parent, args) => books.find(book => book.id === args.id) }, books: { type: new GraphQLList(BookType), description: 'List of All Books', resolve: () => books }, authors: { type: new GraphQLList(AuthorType), description: 'List of All Authors', resolve: () => authors }, author: { type: AuthorType, description: 'Current Author', args: { id: { type: GraphQLInt } }, resolve: (parent, args) => authors.find(author => author.id === args.id) } }) }) const RootMutationType = new GraphQLObjectType({ name: 'Mutation', description: 'Root Mutation', fields: () => ({ addBook: { type: BookType, description: 'Add book', args: { name: { type: GraphQLNonNull(GraphQLString) }, authorId: { type: GraphQLNonNull(GraphQLInt) } }, resolve: (parent, args) => { const book = { id: books.length + 1, name: args.name, authorId: args.authorId } books.push(book) return book } }, addAuthor: { type: AuthorType, description: 'Add author', args: { name: { type: GraphQLNonNull(GraphQLString) } }, resolve: (parent, args) => { const author = { id: authors.length + 1, name: args.name } authors.push(author) return author } } }) }) const schema = new GraphQLSchema({ query: RootQueryType, mutation: RootMutationType }) app.use('/graphql', expressGraphQL({ schema: schema, graphiql: true })); app.listen(5001, () => console.log('codcodile GQL tut is running'))
טוב אז יש לנו פה כמה דברים חשובים מאד שתדעו! והם:
- GraphQLObjectType – מגדיר את הטייפים של המידע שלכם.
- GraphQLSchema – מגדיר את ה schema.
- GraphQLString , GraphQLList ,GraphQLInt , GraphQLNonNull – טייפים של GQL נוספים .
- Query
- Mutation
GraphQLObjectType
טייפ זה חיוני מאד בעבודה עם GQL , למשל אפשר לראות בדוגמה שלנו שהוא מגדיר את הטייפים וגם מאפשר לייצר טייפים בתוך טייפים כמו למשל RootQueryType שהוא הראשי שאוסף לתוכו את הטייפים כגון: AuthorType ,
GraphQLSchema
בסופו של דבר כל ההגדרות של המידע נשפכות לתוך ה Schema שהוא צריך להיות מסוג GraphQLSchema . כמו בקוד שלנו בשורה 130.
GraphQLString , GraphQLList ,GraphQLInt , GraphQLNonNull
טייפים חיוניים לעבודה עם GQL . שימו לב בקוד שלנו היכן בחרנו להשתמש בהם. למשל ID יהיה מטייפ GraphQLInt בעוד משיידע שהוא סטרינגי יהיה GraphQLString . הטייפ GraphQLNonNull מאפשר לנו להגדיר ערך שלא יכול להיות null.
Query
אפשר לראות שהחל משורה 65 בקוד שלנו משהו מעניין מאוד. למעשה זה חלק מהותי בעבודה עם GQL . נבחר להגדיר סוג של מידע שהינו עבור שאילתות לקבלת המידע ולכן ניתן לאשם 'Query' (יכול להיות כל שם שתבחרו בגדול). ואז נגדיר את הסדרות ואופי הבאת הנתונים למשל בשורה 69 נרשום book שמייצג עבורנו כי אנחנו רוצים לקבל מידע של book אחד בלבד. ולכן נרצה להוסיף args שמביא מידע ייחודי לספר (id במקרה הזה) ונמשיך לשורה 75 ונשתמש בפונקציה חשובה לא פחות בשם resolve שלמעשה תעשה פעולה שאנחנו מכירים של find by id ותחזיר את המדיע.
בהמשך נגיד לשורה 77 , שמה נראה כי אנחנו מגדירים ערך של books , כלומר את כל המידע של הספרים. אפשר לראות שיש שימוש בטייפ של רשימה : GraphQLList.
והפעם resolve יחזיר את כל הספרים.
בהמשך תראו משהו מאד דומה עבור authors , author
Mutation
מוטשיין מוטשיין מוטיישן… מאפשר לנו לכתוב או למחוק מהדטה בייס . בדוגמה שלנו אפשר לראות למשל בשורה 89 והילך כיצד לבצע פונקציה שיודעת לכתוב ספר חדש \ כותב חדש לדטה בייס.
נמשיך עם צד לקוח REACT וההתקנות של GQL .
לאחר סיום ההתקנה של React נתכין את הדברים הבאים:
npm install @apollo/client graphql
ועכשיו נתחיל במימוש בסיסי של GQL ו APOLLO בapp.js
import './App.css'; import Books from './books'; import { InMemoryCache, ApolloProvider, createHttpLink, ApolloClient } from '@apollo/client'; const client = new ApolloClient({ uri: 'http://localhost:5001/graphql', cache: new InMemoryCache(), }) function App() { return ( <ApolloProvider client={client}> <Books /> </ApolloProvider> ); } export default App;
apollo/client עוזר לנו לממש GQL בצד לנוח. באתר שלהם אפשר לראות המון דוגמאות ודוקומנטציה סופר נוחה וקלה להבנה.
לפני שנחשוף את הקומפוננטה של books נעבור למשהו יותר חשוב , כיצד אנו מושכים את המידע בעזרת שאילתה של GQL
זה נראה כך , נכין קובץ נפרד לשאילתות לשם הנוחות:
import { gql } from '@apollo/client'; const GET_ALL_BOOKS_DATA = gql` query { books { id name author { name } } } `; export {GET_ALL_BOOKS_DATA};
נעשה שימוש בשאילתא הזו בקומפוננטה של books הרי השיאלתא מביאה את המידע של books
וכן עושים זאת:
import React , {useEffect, useState} from 'react' import {useQuery} from '@apollo/client'; import {GET_ALL_BOOKS_DATA} from './graphql/queries'; import Book from './book'; export default function Books() { const { loading, error, data } = useQuery(GET_ALL_BOOKS_DATA); const [state, setstate] = useState(null); useEffect(() => { if(loading){ }else{ setstate(displayLaunches(data)); } },[data]) function displayLaunches(data){ return data.books.map((book)=>{ return (<Book key={book.id} bookData={book} />) }) } return ( <div> {state} </div> ) }
useQuery – הפונקציה שתטפל בשאילתא ותחזיר לנו את המידע מוכן. נעש שימוש בuseeffect בשביל לקבל את המידע ברגע שיתעדכן.
וזהו נוכל להציג את המידע שמגיע מהשאילתא בעזרת קומפוננטה פשוטה book.
import React from 'react' export default function Book({bookData}) { return ( <div> {bookData.name} {bookData.author.name} </div> ) }
סיכום מדריך GQL
כמובן שזה לא הכל. זה למעשה על קצה המזלג ביכולותיו של GQL . אך זהו שיעור בסיסי וחשוב מאד להכיר וכל על מנת להמשיך ולהעמיק לתוך GQL ונצל את היכולות הגדולות שלו אצלכם בקוד. בהמשך צפו לפוסטים נוספים בGQL ושם נעמיק יותר . לרוב אשלב אותו בפרויקטים של מדריכים וכדומה. בהצלחה תנינים 🐊🐊🐊