Featured image of post GraphQL In a Nutshell

GraphQL In a Nutshell

GraphQL In a Nutshell

GraphQL In a Nutshell

What is GraphQL? A Short, Sweet, and Possibly Over-Caffeinated Introduction

Once upon a time (okay, in 2012), a team of engineers at Facebook had a problem: their mobile apps were slower than a turtle stuck in peanut butter.

The reason? Their REST APIs were fetching way more data than necessary.

So, they put their heads together and said, “What if we could just ask for exactly what we need?”

Boom. GraphQL was born. It was publicly released in 2015, and since then, it has taken over the API world like a cat video goes viral on the internet.

Microservices Applications

Today, a common development pattern is decomposing applications into independent microservices, which communicate via APIs. As the number of microservices in the environment increases, GraphQL offers various benefits:

  • Efficient request routing: GraphQL helps optimize how requests are distributed across microservices.
  • Graceful error handling: Queries can fail partially while still returning useful data.
  • Parallel request execution: A single GraphQL query can fetch data from multiple microservices in parallel.

Instead of multiple endpoints, GraphQL consolidates all API requests into one and routes them internally, making API interactions simpler and faster.

GraphQL Schema Definition Language (SDL)

GraphQL has a Schema Definition Language (SDL) that defines the structure of your API. The schema describes types, queries, mutations, and subscriptions, making it easy to understand what data is available.

Here’s an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type User { 
  id: ID! 
  name: String! 
  email: String! 
  friends: [User] 
} 

type Query { 
  user(id: ID!): User 
  allUsers: [User] 
} 

type Mutation { 
  createUser(name: String!, email: String!): User 
}

Type System

The root type of a GraphQL schema is usually Query, and it contains all the available fields that can be queried. Other types define objects and fields that the GraphQL server can return.

Scalars & Lists

GraphQL has scalar types like:

  • String
  • Int
  • Float
  • Boolean
  • ID (used for unique identifiers)

By default, fields are nullable, meaning they can return null. If you want a field to be non-nullable, use an exclamation mark (!).

1
2
3
4
5
type Book { 
  title: String!  # Required field 
  author: String  # Nullable field 
  genres: [String]  # List of strings 
}

Rapid Application Prototyping

When prototyping new applications, speed is everything. GraphQL improves development productivity by:

  • Allowing frontend teams to work independently from backend teams.
  • Acting as a wrapper around existing APIs, adding features like authentication and authorization.
  • Reducing the need for backend modifications just to accommodate frontend changes.

This makes GraphQL a common choice for startups, where development speed is crucial.

GraphQL vs REST

REST endpoints often return too much or too little data. GraphQL lets the client decide exactly what data is needed.

Example: REST vs GraphQL

  • REST API Calls:

    1. /users → Returns a list of users.
    2. /users/{id}/comments → Returns comments for a user.
    3. /comments/{id}/details → Returns details for a comment.
  • GraphQL Query:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
query {
  users {
    name
    comments {
      content
      details {
        timestamp
      }
    }
  }
}
  • Result: One request instead of three.

GraphQL Resolvers

Resolvers handle fetching data for fields. They execute only when needed, making them powerful for lazy-loading data.

1
2
3
4
5
6
7
8
9
@Resolver(() => Movie)
export class MovieResolver {
    constructor(private movieCommentService: MovieCommentService) {}

    @ResolveField('movieComment', () => [MovieComment])
    async getMovieComment(@Parent() movie: Movie) {
        return this.movieCommentService.getAllMovieCommetsByMovieId(movie.id);
    }
}

Resolvers reduce database load and prevent unnecessary queries.

Global Object Identification

GraphQL provides Global Object Identification to enable consistent caching and object lookups. This ensures that GraphQL clients can efficiently store and refetch objects.

How It Works

For this system to work, GraphQL requires a standardized way of querying and retrieving objects via unique identifiers.

Example query:

1
2
3
4
5
6
7
8
{
  node(id: "4") {
    id
    ... on User {
      name
    }
  }
}

Node Interface

The Node interface ensures every object can be retrieved by a unique ID:

1
2
3
4
5
6
7
8
9
# An object with a Globally Unique ID
interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  name: String!
}
  • The id field ensures that objects can be uniquely identified and refetched.
  • A node query allows fetching any object by its ID, ensuring easy caching.

Introspection for Nodes

A GraphQL server implementing Node correctly will allow introspection queries like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  __type(name: "Node") {
    name
    kind
    fields {
      name
      type {
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

Response:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "__type": {
    "name": "Node",
    "kind": "INTERFACE",
    "fields": [
      {
        "name": "id",
        "type": {
          "kind": "NON_NULL",
          "ofType": {
            "name": "ID",
            "kind": "SCALAR"
          }
        }
      }
    ]
  }
}

Stability in Object Identification

If two objects have identical IDs, they must be equal in every field.

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  fourNode: node(id: "4") {
    id
    ... on User {
      name
      userWithIdOneGreater {
        id
        name
      }
    }
  }
  fiveNode: node(id: "5") {
    id
    ... on User {
      name
      userWithIdOneLess {
        id
        name
      }
    }
  }
}

Response:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "fourNode": {
    "id": "4",
    "name": "Mark Zuckerberg",
    "userWithIdOneGreater": {
      "id": "5",
      "name": "Chris Hughes"
    }
  },
  "fiveNode": {
    "id": "5",
    "name": "Chris Hughes",
    "userWithIdOneLess": {
      "id": "4",
      "name": "Mark Zuckerberg"
    }
  }
}

Since fourNode.id and fiveNode.userWithIdOneLess.id are the same, their names must also match.

Plural Identifying Root Fields

GraphQL supports plural root fields to fetch multiple objects at once.

Example:

1
2
3
4
5
{
  usernames(usernames: ["zuck", "moskov"]) {
    id
  }
}

Response:

1
2
3
4
5
6
{
  "usernames": [
    { "id": "4" },
    { "id": "6" }
  ]
}
  • The order of responses must match the order of requests.
  • This ensures efficient bulk-fetching.

Global Object Identification helps

  • Standardize object lookups across all GraphQL implementations.
  • Enable caching & refetching via a unique id.
  • Improve performance when dealing with multiple related objects.

GraphQL Best Practices

1. Ensure Naming Consistency in the Schema

  • Use camelCase for fields and PascalCase for types.
  • Consistent naming makes queries easier to read.

2. Consider Future Modifications to the Schema

  • GraphQL schemas will evolve. Design with extensibility in mind.
  • Avoid breaking changes that require remapping everything.

3. Eliminate Illogical Fragments

  • Fragments should be meaningful and simplify queries, not complicate them.
  • Avoid unnecessary or redundant fragments.

4. Avoid Hard-Coded Arguments

  • Use variables, not hard-coded parameters.
  • Hard-coding exposes sensitive data and hurts caching performance.

5. Establish an Error Handling Plan

  • Expect failure scenarios and handle them gracefully.
  • Error messages should be informative so debugging is easier.

References