DEV Community

TechBos😎 for getd.io/ - Postman without native apps

Posted on • Edited on

💡Reinvent GraphQL using RESTful - Learn GraphQL from the perspective of RESTful in 4 mins.

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 with name
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 and age 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 a User or Post contains, and use a root Query type to define how client can query data using params such as userId.
# 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 as TypeScript or Flow to make sure client code follows the schema. For example, if we access user's age without null-checking, or if we query a user object with a string-type id, 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)