For beginners, GraphQL may seem complex on the surface but it really isn't. This post shows how you can learn core concepts of GraphQL by progressively inventing them in RESTful API.
Let's go back to year 2014. GraphQL isn't a thing yet and our AWESOME-REST-API server has just gone live with a simple RESTful API that returns user name
by id
:
// A REST API to get user name by user id
GET /user/:id/name
// e.g., get user 123's name
GET /user/123/name
> techbos
One day we decide to also return user's age
. So we come up with 4 options:
- Option A: create a new endpoint for
age
GET /user/123/age
> 23
- Option B: merge
age
withname
GET /user/123/nameAndAge
> { "name": "techbos", "age": 23 }
- Option C: return the entire
user
object and let client pick whichever fields they want
GET /user/123
> { "name": "techbos", "age": 23, "id": 123, "location": "Seattle", ... }
- Option D: use query params
GET /user/123?fields=name,age
> { "name": "techbos", "age": 23 }
It's easy to see option A, B, C has its issues:
- Option A: Fetching
name
andage
requires client to make two http requests. - Option B: Over time we can end up with hundreds of 'combination' endpoints.
- Option C: A lot of wasted bandwidth.
Option D seems reasonable. So we decide to give it a try and improve it as we go:
Use a JSON-like Request Body
Firstly, ?fields=name
works fine for simple fields, but gets complicated when we have object/nested fields, e.g., first
and last
of name
object below:
{
"name": {
"first": "tech",
"last": "bos",
}
}
To solve this, we replace the url query param ?fields=
with a JSON-like request body. We also use POST
instead of GET
.
// Replace ?fields=name,age with request body
POST /user/123
// JSON-like request body for selecting fields
{
name {
first
}
age
}
// response
> {
"name": {
"first": "tech"
},
"age": 23
}
This gives us a clean way of telling server which fields to return. Neat.
Batch Multiple Requests
Next, we notice that our client code always fetches user
and posts
at same time using two separate requests:
// Get user 123's first name and age
POST /user/123 { name { first }, age }
// Get user 123's post title and content
POST /posts/user/123 { title, content }
Can we do it with only one request? Sure. We can merge them into one single request body, and use a single endpoint /smartql
, which stands for 'Smart Query Language':
POST /smartql
// One request body to query for two things
{
// Replaces /user/123
user(id: 123) {
name {
first
}
age
}
// Replaces /posts/user/123
posts(userId: 123) {
title
content
}
}
// response
> {
"user": {
"name": {
"first": "tech"
},
"age": 23,
},
"notifications": [{
"title": "Notification 1",
"content": "Super important",
}, {
"title": "Notification 2",
"content": "Not very important",
}],
}
Now we can batch-fetch multiple endpoints in a single request. Neat.
Make API Strong Typed
As our API grows, it becomes more and more difficult for front- and back-end engineers to sync up on the API changes. Our API doc seems always outdated, and it's easy to cause bugs with any API changes.
For example, we change the age
field to null-able, and this causes a lot of client-side crashes because the code assumes age
to always be valid. To fix this:
- First, on the server side, we create a data
Schema
and use that to make sure any data that goes in or out of our server must follow the expected format. E.g., we can define what fields aUser
orPost
contains, and use a rootQuery
type to define how client can query data using params such asuserId
.
# Data type for a user
type User {
name: Name! # non-null!
age: Int # nullable
}
# Data type for a user name
type Name {
first: String
last: String
}
# Data type for a notification
type Post {
title: String!
content: String
}
# Root query type for request body
type Query {
user(id: Int!): User
posts(userId: Int!): [Post] # an array of Posts
}
- Next, on the client side, we download all the type schemas from server into a
schema.smartql
file, and use static code checking tools such asTypeScript
orFlow
to make sure client code follows the schema. For example, if we access user'sage
without null-checking, or if we query auser
object with a string-typeid
, we will get an error from the type checking.
With this solution we never need to maintain an API doc, as the API itself becomes a living doc.
We are all so very much happy about this new SmartQL
. After a year, we decide to open source it under the name of GraphQL
because why not. And this is how GraphQL all started...
Just kidding 🙃
I hope this post help you understand the key concepts of GraphQL. You can play with the sample GraphQL queries above in 🏀 getd.io's playground 🏀
If you enjoy this post, please click ❤️ and follow me on twitter @tech_bos!
Top comments (0)