There's a whole world of graphql stuff based around supporting next-gen fancy React clients. I haven't even skimmed the surface, but the existence of these things must be mentioned before discussing more basic client consumption of a graphql endpoint.
Relay is a graphql client framework for React apps. The use of Relay also has server-side implications around how you design and implement your schema (connections, nodes, edges) - it's out of the scope of this 'basics' workshop.
The third thing graphql supports, in addition to queries and mutations, is subscriptions via the WebSockets protocol. This of course requires some kind of underlying Pub/Sub implementation and some additional server-side work.
Apollo has an comprehensive tutorial on these subjects if you want to learn more.
Let's get back to the basics...
For more traditional consumption, it's simply a case of request and response with JSON payloads. You don't need to use or write any special client to call graphql endpoints, but let's write one anyway.
-
We'll use
axiosas our HTTP client.npm install axios -s npm install @types/axios -D -
Here's an example of a very basic class to assist query operations. Create the following file:
client/DataService.tsimport axios from "axios"; export default class DataService { private url: string; constructor(url: string) { this.url = url; } public async query<T, TVariables>( query: string, variables?: TVariables ): Promise<T> { return axios .post(this.url, { query, variables }) .then(response => response.data as T); } }
-
Now write a program to call your API:
client/app.tsimport DataService from "./DataService"; const dataService = new DataService("http://localhost:3000/graphql"); const query = ` { artist(id: 1) { name albums { title tracks { name composer milliseconds bytes unitPrice } } } }`; dataService .query(query) .then(console.log) .catch(console.log);
-
Create a new launch config to run your client app:
{ "type": "node", "request": "launch", "name": "debug client app", "runtimeArgs": [ "--inspect-brk=9220", "--nolazy", "-r", "ts-node/register", "${workspaceFolder}/client/app.ts" ], "cwd": "${workspaceFolder}/client", "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "port": 9220 },
- First run your graphql API via the
debuglaunch config. - Then run your client app via the
debug client applaunch config.
✔ You should see console output as you'd expect:
{ data: { artist: { name: "AC/DC", albums: [Array] } } }
-
Change your query to a named and paramaterised query, in a file:
client/queries/artist-albums-tracks.graphqlquery ArtistAlbumsTracks($artistId: Int!) { artist(id: $artistId) { name albums { title tracks { name composer milliseconds bytes unitPrice } } } }
-
Change your client to use the new query and supply the input via a variables map:
import DataService from "./DataService"; import * as fs from "fs"; const dataService = new DataService("http://localhost:3000/graphql"); dataService .query(fs.readFileSync("queries/artist-albums-tracks.graphql", "utf8"), { artistId: 1 }) .then(console.log) .catch(console.log);
-
Run your updated client, the output should be the same.
Step # 3 - Use Apollo GraphQL code generator to generate types
It makes sense that in a Typescript app we'd want to type the query input and output. right?
How can we do this when we declare our return type differently for every query? Should we just create a superset return type and make everything nullable?
Ummm, no, we can use the apollo-codegen package to generate types based on the schema and our query - both are parseable and well defined.
-
Install apollo-codegen
npm install apollo-codegen -D -
Introspect and download the remote schema to a local file.
.\node_modules\.bin\apollo-codegen introspect-schema http://localhost:3000/graphql --output client/schema.json -
Create a directory for generated types:
client/typese.g.
mkdir client/types -
Generate input and output types, based on your query and the downloaded schema.
.\node_modules\.bin\apollo-codegen generate client/queries/artist-albums-tracks.graphql --schema client/schema.json --target typescript --output client/types/artist-albums-tracks.ts -
Change your code to use the input and output types. The types are named according to your query name:
[query-name]Queryfor output, and[query-name]QueryVariablesfor input.dataService .query<ArtistAlbumsTracksQuery,ArtistAlbumsTracksQueryVariables>( fs.readFileSync("queries/artist-albums-tracks.graphql", "utf8"), { artistId: 1 });
-
Import those types, run your updated client. The output should be the same, but you now have an output type definition to use throughout your client plus intellisense on the input!
✔ Go forth and reap the benefits of type definitions without having to write them!
That concludes the final lesson in this tutorial, I hope it helped gain an understanding of the basics of building a GraphQL API.
Please feel free to provide feedback or contribute via a pull request.