Introduction & Quick Guide to GraphQL for BackEnd & FrontEnd
GraphQL is an API query language. It provides a runtime to describe & query the data for any storage. Here is an Introduction & Quick guide on GraphQL
GraphQL is an API query language. It provides a runtime to describe and query the data, regardless of the storage engine.
GraphQL's benefits include
- Typed data
- Get what you asked for
- Multiple data requests in one call
- One endpoint, changes in API are now a bit easier
- Subscriptions
- etc.
For example, consider a system similar to a social network where we have users, and they have other users as friends. The following entity will completely encapsulate the data model; how it will be dealt with in Back End and Front End will be seen after that.
type User {
id: String!
fullName: String
name: Name
email: String!
friends: [User]
}
type Name {
first: String
last: String
}
For this data description, the front end will send the request to data as follows:
{
user(id: "5a826fdf9856ad6503d728d5") {
id
name {
first
last
}
email
friends {
id
email
}
}
}
And will receive the response as follows:
{
"data": {
"user": {
"id": "5a826fdf9856ad6503d728d5",
"name": {
"first": "Stanton",
"last": "Hansen"
},
"email": "[email protected]",
"friends": [
{
"id": "5a826fdf0d054a6fab7bb067",
"email": "[email protected]"
},
...
{
"id": "5a826fdf0d054a6fab7bb067",
"email": "[email protected]"
}
]
}
}
}
And the good part is that the data can be from any interface, be it the RDBMS, NoSQL DB, a File or even an in-memory operation.
GraphiQL
GraphiQL is a GUI tool to explore the GraphQL API. It lets you design your queries and play with the combinations and other things for the provided endpoint.
This tool also serves as Documentation for the GraphQL API.
For the above queries, the following is the GraphiQL view:
In the above screenshot, you can see the following:
- design your query in the left panel
- see the documentation in the right panel
- see the data response in the middle panel
You can use the variables to optimize your queries, as shown in the following way; the query can be precisely the same in GraphiQL and the FrontEnd, variables are passed separately:
Back End
Let's take a deep look at the following example of graphql with express
, a node.js
server framework.
Dependencies:
npm i -S apollo-server-express graphql-tools graphql express body-parser uuid node-fetch
Server:
const express = require('express'); const bodyParser = require('body-parser'); const { graphqlExpress, graphiqlExpress } = require('apollo-server-express'); const { makeExecutableSchema } = require('graphql-tools'); // The GraphQL schema in string form const typeDefs = require('./schema'); // The resolvers const resolvers = require('./resolvers'); // Put together a schema const schema = makeExecutableSchema({ typeDefs, resolvers, }); // Initialize the app const app = express(); // The GraphQL endpoint app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })); // GraphiQL, a visual editor for queries app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })); // Start the server app.listen(3000, () => { console.log('Go to http://localhost:3000/graphiql to run queries!'); });
Schema:
module.exports = ` type Mutation { addUser(first: String, last: String, email: String, picture: String, friends: [String]): User } type Query { users: [User] user(id: String!): User } type User { id: String! fullName: String name: Name email: String! friends: [User] } type Name { first: String last: String } `;
Resolvers:
const uuid = require('uuid/v4'); // Some fake data const users = require('./users'); const friendsMapper = id => (Object.assign( {}, users[id], { friends: users[id].friends.map(i => users[i]) } )); // The resolvers module.exports = { Query: { users: () => Object.values(users).map(u => friendsMapper(u.id)), user: (_, {id}) => friendsMapper(id), }, Mutation: { addUser: (_, data) => { const uid = uuid(); users[uid] = Object.assign({}, data, {id: uid}) return friendsMapper(uid); } } };
And Data:
{ "5a826fdf27256fa9ba444cd4": { "id": "5a826fdf27256fa9ba444cd4", "email": "[email protected]", "fullName": "Nancy Palmer", "name": { "last": "Palmer", "first": "Nancy" }, "picture": "http://placehold.it/32x32", "friends": [ "5a826fdf50f698c2d9c492ca", "5a826fdf40b7d805ca2d6e75", ... ] }, ... }
As the server.js
is executed, the server will be up at port 3000
and then graphiql
& graphql
endpoints are available to use.
Brief Steps
Lets review above code in brief steps:
- create schema definition as a string (see
schema.js
); schema has following:- types: the entities which are going to be used in the GraphQL
- Query type: this is a root type for all the queries that the API consumer can request
- Mutation type: this will contain all the data modification queries
- create resolvers to define how data I/O should be done; this will have following in an object
- Query: all the query resolvers will reside under this key and should match the Query type in schema
- Mutation: same as queries, all the mutation will reside under this and should match with what is there in the Mutation type in schema
- make the schema executable by
makeExecutableSchema
- attach the route
/graphql
to the express by the apollo plugin asapp.use('/graphql', bodyParser.json(), graphqlExpress({ schema }));
- attach the
/graphiql
route asapp.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }));
- start the server ?
As above code list the example of data being seeded through JSON file and then completely in memory.
REST Endpoints in GraphQL
You can also make the resolver to fetch the data form an endpoint and then return it, consider following schema and resolver:
const typeDefs = ` type Post { id: Int! userId: Int title: String body: String } type Query { posts: [Post] } `; const resolvers = { Query: { posts: async () => fetch('https://jsonplaceholder.typicode.com/posts') .then(response => response.json()) } } module.exports = { typeDefs, resolvers }
And the queries can be used as shown in following screenshot:
And in similar manner you can make it work with other data tools like mongodb, SQL etc.
Front End
From front end point of view, the use of graphql is pretty simple as there are not many things changing; and is approximately similar effort.
Though any change in requirement of data will introduce change in the request-query as well; which in case is good because the errors are intended and hence fixed right away.
Lets take a look at basic vanilla JS code to get data and show it on front end. For certain, this code will be passed through bundler like webpack
or rollup
.
Dependencies:
npm i -D webpack@next webpack-cli
npm i -S graphql graphql-tag apollo-cache-inmemory apollo-client apollo-link-http
Code:
import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; import gql from 'graphql-tag'; const client = new ApolloClient({ link: new HttpLink({ uri: 'http://localhost:3000/graphql' }), cache: new InMemoryCache() }); client.query({ query: gql` query users { users { id fullName email } } `, }) .then(({data}) => appendData(data)) .catch(error => console.error(error)); const appendData = (data) => { console.log(data); const app = document.querySelector('#app'); app.innerHTML = ''; data.users.map(user => { const d = document.createElement('div'); d.innerHTML = `<div class="user"> <h3>${user.fullName}</h3> <p>${user.email}</p> </div>`; app.appendChild(d.firstElementChild); }) }
And it will look like following in the HTML page
Conclusion
GraphQL is considered a replacement or alternative to REST API. Above examples show a quick configuration and usage of GraphQL on both BackEnd and FrontEnd; the actual usage requires more configuration and proofing. The real world use case also involves complex schema and resolvers, which will pose you different challenges.
Let us know your experiences and troubles while using GraphQL via comments and social network.
Thank you!