Nem szabad félnem. A félelem az elme gyilkosa. A félelem az a kis halál, amely teljes megsemmisülést hoz. Szembesülni fogok a félelmemmel. Hagyom, hogy áthaladjon rajtam és rajtam keresztül. És amikor elmúlt, megfordítom a belső szemet, hogy lássam az útját. Ahol a félelem eltűnt, nem lesz semmi. Csak én maradok.
- "Litánia a félelem ellen", Frank Herbert, Dune
Lehet, hogy kíváncsi vagy: "Mi köze a félelemnek a React alkalmazáshoz?" Először is, a React alkalmazásban nincs mitől tartani. Valójában ebben a konkrét alkalmazásban betiltottuk a félelmet. Hát nem szép?
Most, hogy készen állsz a rettentésekre, beszéljük meg az alkalmazást. Ez egy mini Yelp klón, ahol az éttermek áttekintése helyett a felhasználók a klasszikus sci-fi sorozat, a Dune bolygóit tekintik át. (Miért? Mert új Dune film jelenik meg ... de visszatérünk a fő pontra.)
A teljes kötegű alkalmazásunk felépítéséhez olyan technológiákat használunk, amelyek megkönnyítik az életünket.
- Reagál: Intuitív, kompozíciós front-end keret, mert agyunk szereti a dolgokat komponálni.
- GraphQL: Számos okot hallhatott, amiért a GraphQL fantasztikus. Messze a legfontosabb a fejlesztői termelékenység és a boldogság .
- Hasura: Állítson be automatikusan generált GraphQL API-t a Postgres adatbázis tetejére 30 másodperc alatt.
- Heroku: Adatbázisunk tárolása.
És a GraphQL hogyan nyújt boldogságot nekem?
Úgy látom, szkeptikus vagy. De nagy valószínűséggel azonnal előkerül, amint egy kis időt tölt a GraphiQL-lel (a GraphQL játszótér).
A GraphQL használata egyszerű a front-end fejlesztő számára, összehasonlítva a nehézkes REST végpontok régi módjaival. A GraphQL egyetlen végpontot ad, amely meghallgatja az összes problémáját ... mármint a lekérdezéseket. Olyan nagyszerű hallgató, hogy pontosan meg tudja mondani neki, mit akar, és megadja neked, sem kevesebbet, sem többet.
Pszichésnek érzi ezt a terápiás élményt? Merüljünk el a bemutatóban, hogy ASAP kipróbálhassa!
?? Itt van a repo, ha szeretné kódolni.
P art 1: S earch
S TEP 1: D eploy a Heroku
Minden jó út első lépése egy forró teával való leülés és nyugodt kortyolgatás. Miután ezt megtettük, a Hasura webhelyről telepíthetjük Herokuba. Ez felkészít minket mindenre, amire szükségünk van: egy Postgres adatbázissal, a Hasura GraphQL motorunkkal és néhány rágcsálnivalóval az utazáshoz.

2. lépés: Hozzon létre bolygótáblát
Felhasználóink szeretnék áttekinteni a bolygókat. Tehát létrehozunk egy Postgres táblázatot a Hasura konzolon keresztül bolygónk adatainak tárolására. Figyelemre méltó a gonosz bolygó, a Giedi Prime, amely szokatlan konyhájával hívta fel a figyelmet.

Eközben a GraphiQL fülön: A Hasura automatikusan létrehozta a GraphQL sémánkat! Játsszon itt az Intézővel ??

S TEP 3: C reate React app
Szükségünk lesz egy felhasználói felületre az alkalmazásunkhoz, ezért létrehozunk egy React alkalmazást, és néhány könyvtárat telepítünk a GraphQL kérésekhez, útválasztáshoz és stílusokhoz. (Győződjön meg róla, hogy először telepítette a Node-ot.)
> npx create-react-app melange > cd melange > npm install graphql @apollo/client react-router-dom @emotion/styled @emotion/core > npm start
S TEP 4: S et fel Apolló Client
Az Apollo Client segít nekünk a GraphQL hálózati kéréseinkben és a gyorsítótárban, így elkerülhetjük ezt a morgást. Első lekérdezésünket is elvégezzük, és felsoroljuk a bolygókat! Az alkalmazásunk kezd kialakulni.
import React from "react"; import { render } from "react-dom"; import { ApolloProvider } from "@apollo/client"; import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; import Planets from "./components/Planets"; const client = new ApolloClient({ cache: new InMemoryCache(), link: new HttpLink({ uri: "[YOUR HASURA GRAPHQL ENDPOINT]", }), }); const App = () => ( ); render(, document.getElementById("root"));
Mielőtt másoljuk-beillesztenénk a kódunkba, teszteljük a GraphQL lekérdezésünket a Hasura konzolon.

import React from "react"; import { useQuery, gql } from "@apollo/client"; const PLANETS = gql` { planets { id name cuisine } } `; const Planets = ({ newPlanets }) => { const { loading, error, data } = useQuery(PLANETS); if (loading) return Loading ...
; if (error) return Error :(
; return data.planets.map(({id, name, cuisine}) => ( {name} | {cuisine}
)); }; export default Planets;
S tep 5: S tyle list
Bolygónk listája szép és minden, de egy kis átalakításra szorul az Emotion (a teljes stílusokat lásd a repóban).

S tep 6: Keresési forma és állapot
Felhasználóink bolygókat akarnak keresni és név szerint rendezni őket. Tehát hozzáadunk egy keresési űrlapot, amely lekérdezi a végpontunkat egy keresési karakterlánccal, és továbbítja az eredményeket Planets
a bolygó listánk frissítéséhez. A React Hooks segítségével is kezelhetjük az alkalmazás állapotát.
import React, { useState } from "react"; import { useLazyQuery, gql } from "@apollo/client"; import Search from "./Search"; import Planets from "./Planets"; const SEARCH = gql` query Search($match: String) { planets(order_by: { name: asc }, where: { name: { _ilike: $match } }) { name cuisine id } } `; const PlanetSearch = () => { const [inputVal, setInputVal] = useState(""); const [search, { loading, error, data }] = useLazyQuery(SEARCH); return ( setInputVal(e.target.value)} onSearch={() => search({ variables: { match: `%${inputVal}%` } })} /> ); }; export default PlanetSearch;
import React from "react"; import { useQuery, gql } from "@apollo/client"; import { List, ListItem } from "./shared/List"; import { Badge } from "./shared/Badge"; const PLANETS = gql` { planets { id name cuisine } } `; const Planets = ({ newPlanets }) => { const { loading, error, data } = useQuery(PLANETS); const renderPlanets = (planets) => { return planets.map(({ id, name, cuisine }) => ( {name} {cuisine} )); }; if (loading) return Loading ...
; if (error) return Error :(
; return renderPlanets(newPlanets ; }; export default Planets;
import React from "react"; import styled from "@emotion/styled"; import { Input, Button } from "./shared/Form"; const SearchForm = styled.div` display: flex; align-items: center; > button { margin-left: 1rem; } `; const Search = ({ inputVal, onChange, onSearch }) => { return ( Search ); }; export default Search;
import React from "react"; import { render } from "react-dom"; import { ApolloProvider } from "@apollo/client"; import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; import PlanetSearch from "./components/PlanetSearch"; import Logo from "./components/shared/Logo"; import "./index.css"; const client = new ApolloClient({ cache: new InMemoryCache(), link: new HttpLink({ uri: "[YOUR HASURA GRAPHQL ENDPOINT]", }), }); const App = () => ( ); render(, document.getElementById("root"));
S tep 7: B e büszke
Már megvalósítottuk bolygónk listáját és keresési funkcióit! Szeretettel tekintünk a kézimunkánkra, készítünk együtt néhány szelfit, és továbblépünk a felülvizsgálatokra.

P Art 2: L ive értékelés
S tep 1: C reate vélemény táblázat
Felhasználóink meglátogatják ezeket a bolygókat, és véleményeket írnak tapasztalataikról. A Hasura konzolon keresztül létrehozunk egy táblázatot a felülvizsgálati adatokhoz.

We add a foreign key from the planet_id
column to the id
column in the planets
table, to indicate that planet_id
s of reviews
have to match id
's of planets
.

Step 2: Track relationships
Each planet has multiple reviews, while each review has one planet: a one-to-many relationship. We create and track this relationship via the Hasura console, so it can be exposed in our GraphQL schema.

Now we can query reviews for each planet in the Explorer!

Step 3: Set up routing
We want to be able to click on a planet and view its reviews on a separate page. We set up routing with React Router, and list reviews on the planet page.
import React from "react"; import { render } from "react-dom"; import { ApolloProvider } from "@apollo/client"; import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; import { BrowserRouter, Switch, Route } from "react-router-dom"; import PlanetSearch from "./components/PlanetSearch"; import Planet from "./components/Planet"; import Logo from "./components/shared/Logo"; import "./index.css"; const client = new ApolloClient({ cache: new InMemoryCache(), link: new HttpLink({ uri: "[YOUR HASURA GRAPHQL ENDPOINT]", }), }); const App = () => ( ); render(, document.getElementById("root"));
import React from "react"; import { useQuery, gql } from "@apollo/client"; import { List, ListItem } from "./shared/List"; import { Badge } from "./shared/Badge"; const PLANET = gql` query Planet($id: uuid!) { planets_by_pk(id: $id) { id name cuisine reviews { id body } } } `; const Planet = ({ match: { params: { id }, }, }) => { const { loading, error, data } = useQuery(PLANET, { variables: { id }, }); if (loading) return Loading ...
; if (error) return Error :(
; const { name, cuisine, reviews } = data.planets_by_pk; return ( {name} {cuisine}
{reviews.map((review) => ( {review.body} ))} ); }; export default Planet;
import React from "react"; import { useQuery, gql } from "@apollo/client"; import { Link } from "react-router-dom"; import { List, ListItemWithLink } from "./shared/List"; import { Badge } from "./shared/Badge"; const PLANETS = gql` { planets { id name cuisine } } `; const Planets = ({ newPlanets }) => { const { loading, error, data } = useQuery(PLANETS); const renderPlanets = (planets) => { return planets.map(({ id, name, cuisine }) => ( {name} {cuisine} )); }; if (loading) return Loading ...
; if (error) return Error :(
; return ; }; export default Planets;
Step 4: Set up subscriptions
We install new libraries and set up Apollo Client to support subscriptions. Then, we change our reviews query to a subscription so it can show live updates.
> npm install @apollo/link-ws subscriptions-transport-ws
import React from "react"; import { render } from "react-dom"; import { ApolloProvider, ApolloClient, HttpLink, InMemoryCache, split, } from "@apollo/client"; import { getMainDefinition } from "@apollo/client/utilities"; import { WebSocketLink } from "@apollo/link-ws"; import { BrowserRouter, Switch, Route } from "react-router-dom"; import PlanetSearch from "./components/PlanetSearch"; import Planet from "./components/Planet"; import Logo from "./components/shared/Logo"; import "./index.css"; const GRAPHQL_ENDPOINT = "[YOUR HASURA GRAPHQL ENDPOINT]"; const httpLink = new HttpLink({ uri: `//${GRAPHQL_ENDPOINT}`, }); const wsLink = new WebSocketLink({ uri: `ws://${GRAPHQL_ENDPOINT}`, options: { reconnect: true, }, }); const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === "OperationDefinition" && definition.operation === "subscription" ); }, wsLink, httpLink ); const client = new ApolloClient({ cache: new InMemoryCache(), link: splitLink, }); const App = () => ( ); render(, document.getElementById("root"));
import React from "react"; import { useSubscription, gql } from "@apollo/client"; import { List, ListItem } from "./shared/List"; import { Badge } from "./shared/Badge"; const PLANET = gql` subscription Planet($id: uuid!) { planets_by_pk(id: $id) { id name cuisine reviews { id body } } } `; const Planet = ({ match: { params: { id }, }, }) => { const { loading, error, data } = useSubscription(PLANET, { variables: { id }, }); if (loading) return Loading ...
; if (error) return Error :(
; const { name, cuisine, reviews } = data.planets_by_pk; return ( {name} {cuisine}
{reviews.map((review) => ( {review.body} ))} ); }; export default Planet;

Step 5: Do a sandworm dance
We've implemented planets with live reviews! Do a little dance to celebrate before getting down to serious business.

Part 3: Business logic
Step 1: Add input form
We want a way to submit reviews through our UI. We rename our search form to be a generic InputForm
and add it above the review list.
import React, { useState } from "react"; import { useSubscription, gql } from "@apollo/client"; import { List, ListItem } from "./shared/List"; import { Badge } from "./shared/Badge"; import InputForm from "./shared/InputForm"; const PLANET = gql` subscription Planet($id: uuid!) { planets_by_pk(id: $id) { id name cuisine reviews(order_by: { created_at: desc }) { id body created_at } } } `; const Planet = ({ match: { params: { id }, }, }) => { const [inputVal, setInputVal] = useState(""); const { loading, error, data } = useSubscription(PLANET, { variables: { id }, }); if (loading) return Loading ...
; if (error) return Error :(
; const { name, cuisine, reviews } = data.planets_by_pk; return ( {name} {cuisine}
setInputVal(e.target.value)} onSubmit={() => {}} buttonText="Submit" /> {reviews.map((review) => ( {review.body} ))} ); }; export default Planet;
Step 2: Test review mutation
We'll use a mutation to add new reviews. We test our mutation with GraphiQL in the Hasura console.

And convert it to accept variables so we can use it in our code.

Step 3: Create action
The Bene Gesserit have requested us to not allow (cough censor cough) the word "fear" in the reviews. We create an action for the business logic that will check for this word whenever a user submits a review.

Inside our freshly minted action, we go to the "Codegen" tab.

We select the nodejs-express option, and copy the handler boilerplate code below.

We click "Try on Glitch," which takes us to a barebones express app, where we can paste our handler code.

Back inside our action, we set our handler URL to the one from our Glitch app, with the correct route from our handler code.

We can now test our action in the console. It runs like a regular mutation, because we don't have any business logic checking for the word "fear" yet.

Step 4: Add business logic
In our handler, we add business logic that checks for "fear" inside the body of the review. If it's fearless, we run the mutation as usual. If not, we return an ominous error.

If we run the action with "fear" now, we get the error in the response:

Step 5: Order reviews
Our review order is currently topsy turvy. We add a created_at
column to the reviews
table so we can order by newest first.
reviews(order_by: { created_at: desc })
Step 6: Add review mutation
Finally, we update our action syntax with variables, and copy paste it into our code as a mutation. We update our code to run this mutation when a user submits a new review, so that our business logic can check it for compliance (ahem obedience ahem) before updating our database.
import React, { useState } from "react"; import { useSubscription, useMutation, gql } from "@apollo/client"; import { List, ListItem } from "./shared/List"; import { Badge } from "./shared/Badge"; import InputForm from "./shared/InputForm"; const PLANET = gql` subscription Planet($id: uuid!) { planets_by_pk(id: $id) { id name cuisine reviews(order_by: { created_at: desc }) { id body created_at } } } `; const ADD_REVIEW = gql` mutation($body: String!, $id: uuid!) { AddFearlessReview(body: $body, id: $id) { affected_rows } } `; const Planet = ({ match: { params: { id }, }, }) => { const [inputVal, setInputVal] = useState(""); const { loading, error, data } = useSubscription(PLANET, { variables: { id }, }); const [addReview] = useMutation(ADD_REVIEW); if (loading) return Loading ...
; if (error) return Error :(
; const { name, cuisine, reviews } = data.planets_by_pk; return ( {name} {cuisine}
setInputVal(e.target.value)} onSubmit={() => { addReview({ variables: { id, body: inputVal } }) .then(() => setInputVal("")) .catch((e) => { setInputVal(e.message); }); }} buttonText="Submit" /> {reviews.map((review) => ( {review.body} ))} ); }; export default Planet;
If we submit a new review that includes "fear" now, we get our ominous error, which we display in the input field.

Step 7: We did it! ?
Congrats on building a full-stack React & GraphQL app!

What does the future hold?

If only we had some spice melange, we would know. But we built so many features in so little time! We covered GraphQL queries, mutations, subscriptions, routing, searching, and even custom business logic with Hasura actions! I hope you had fun coding along.
Milyen további funkciókat szeretne látni ebben az alkalmazásban? Fordulj hozzám a Twitteren, és további oktatóanyagokat készítek! Ha Ön inspirálta, hogy saját maga is hozzáadja a funkciókat, kérjük, ossza meg - szívesen hallanék róluk :)