GraphQL

GraphQL is a query language for APIs that allows clients to request exactly the data they need in a single request. It provides a type system to describe data and offers a more efficient, flexible, and powerful alternative to traditional REST-based architectures. These notes explore the fundamentals of GraphQL, including its anatomy, how it processes queries, common best practices, and example commands illustrating how to interact with a GraphQL server. ASCII diagrams and code blocks provide additional clarity.

What is GraphQL?

GraphQL is a specification and set of tools that enable clients to define the structure of the data required and receive precisely that data from a server. It was initially developed by Facebook to improve the efficiency and flexibility of data fetching in mobile apps, but it has since been widely adopted in various contexts.

The diagram below offers a conceptual view of how a GraphQL client communicates with a GraphQL server, which in turn interacts with one or more data sources (databases, microservices, etc.):

+------------------------+                  +------------------------+
|                        |                  |                        |
|    GraphQL Client      |                  |    GraphQL Server      |
| (Web, Mobile, etc.)    |                  |  with Schema & Resolvers|
+------------------------+                  +-----------+------------+
         | 1. Sends Query or Mutation                  |
         |-------------------------------------------->|
         |                     2. Receives Query       |
         |                        Parses & Validates    |
         |                        Resolves Fields       |
         |                        Fetches Data          |
         |                        from Data Sources     |
         |                     3. Gathers Results       |
         |<--------------------------------------------|
         | 4. Receives Response (Precisely the data needed)
         |
+------------------------+                   
|                        |                   
|  Renders or Processes  |                   
|  the Returned Data     |                   
|                        |                   
+------------------------+                   

The client sends a query or mutation describing the shape of the data it wants, the server resolves that request by retrieving information from data sources, and it sends back exactly what was requested.

Core Concepts of GraphQL

GraphQL centers around three key concepts: the schema, the query language itself, and resolvers. A schema defines the data types and relationships, the query language allows clients to specify what data they need, and resolvers act as functions that map these requests to actual data.

Schema Definition Language (SDL)

Schemas in GraphQL are described using a human-readable syntax called the Schema Definition Language. A schema outlines the types, fields, and relationships. For instance:

type Book {
  id: ID!
  title: String!
  author: Author!
}
 
type Author {
  id: ID!
  name: String!
  books: [Book!]!
}
 
type Query {
  book(id: ID!): Book
  books: [Book!]!
  author(id: ID!): Author
}
 
type Mutation {
  addBook(title: String!, authorId: ID!): Book!
}

This example schema defines a Book type with an id, title, and an author. It also defines a Query type for retrieving books or authors, and a Mutation type for adding new books.

Queries, Mutations, and Subscriptions

GraphQL offers three primary operations:

  • Query for reading data (comparable to GET in REST).
  • Mutation for creating, updating, or deleting data (comparable to POST, PUT, DELETE in REST).
  • Subscription for real-time data feeds, where clients can stay updated about changes.

Resolvers

Resolvers are functions that map the fields in a query to actual backend data. A resolver for Query.book might look like this in JavaScript:

const resolvers = {
  Query: {
    book: (parent, args, context, info) => {
      // parent is the result of the previous resolver in the chain
      // args contains the arguments passed in the query
      // context might hold things like database connections or user info
      // info provides query execution details
      return context.db.getBookById(args.id);
    },
    books: (parent, args, context) => {
      return context.db.getAllBooks();
    }
  },
  Mutation: {
    addBook: (parent, args, context) => {
      return context.db.createBook(args.title, args.authorId);
    }
  },
  Book: {
    author: (parent, args, context) => {
      return context.db.getAuthorById(parent.authorId);
    }
  }
};

Resolvers can also include logic for caching or transformations. They are where much of the server-side computation and fetching takes place.

GraphQL vs REST

GraphQL and REST address similar problems but approach them differently:

  • GraphQL focuses on asking for exactly what is needed in one request.
  • REST typically relies on multiple endpoints that may return more data than required or less than required, forcing the client to make further requests or filter the responses.

A key performance aspect of GraphQL is reducing “over-fetching” and “under-fetching.” Over-fetching happens when a REST endpoint returns unneeded fields, and under-fetching means a client must call multiple endpoints to get all the data it needs.

Query Execution and Performance

A simplistic mathematical way to describe potential benefits is to note that REST might require N endpoints to fulfill a complex request, whereas GraphQL can potentially achieve the same result in a single request. If T_rest denotes the total time for multiple requests and T_graph denotes the single GraphQL request time, and if T_graph < T_rest, then GraphQL can offer performance improvements in network-limited scenarios.

Best Practices for GraphQL

There are several considerations for designing and deploying a GraphQL API:

  • Use schema stitching or federation for large projects where multiple teams own different parts of the data.
  • Implement caching at various layers, such as the resolver level or with data loader patterns, to reduce repetitive database lookups.
  • Keep queries small and focused when possible, but maintain the flexibility that GraphQL offers.
  • Employ schema versioning or deprecation strategies to allow clients to adapt over time.
  • Pay attention to security, especially when exposing GraphiQL or Playground in production environments.

Working with GraphQL from the Client Side

Clients can query a GraphQL server over an HTTP POST request with a JSON body. The table below highlights a few common fields often included in the request:

FieldDescription
queryThe GraphQL query or mutation string
variablesA JSON object holding variable values for the query
operationNameIdentifies which named operation in the request should execute

Example Commands with curl

Below are some practical examples of how to interact with a GraphQL API using curl. Each example command is followed by an example response and a brief interpretation.

  1. Query Single Book
curl -X POST -H "Content-Type: application/json" \
-d '{
  "query": "query GetBook($id: ID!) { book(id: $id) { id title author { name } } }",
  "variables": {"id": "1"}
}' \
http://example.com/graphql
  • Example output:
    {
      "data": {
        "book": {
          "id": "1",
          "title": "GraphQL Handbook",
          "author": {
            "name": "Jane Doe"
          }
        }
      }
    }
  • Interpretation of the output:
    The server returned a JSON object with the requested book data, including the author’s name. The book field matches the query structure.
  1. Query List of Books
curl -X POST -H "Content-Type: application/json" \
-d '{
  "query": "query { books { id title } }"
}' \
http://example.com/graphql
  • Example output:
    {
      "data": {
        "books": [
          { "id": "1", "title": "GraphQL Handbook" },
          { "id": "2", "title": "Learning GraphQL" }
        ]
      }
    }
  • Interpretation of the output:
    The server responded with an array of books, each containing the id and title. No other fields are included because they were not requested.
  1. Mutation: Add a Book
curl -X POST -H "Content-Type: application/json" \
-d '{
  "query": "mutation AddBook($title: String!, $authorId: ID!) { addBook(title: $title, authorId: $authorId) { id title author { name } } }",
  "variables": {"title": "New Book", "authorId": "123"}
}' \
http://example.com/graphql
  • Example output:
    {
      "data": {
        "addBook": {
          "id": "3",
          "title": "New Book",
          "author": {
            "name": "Author Name"
          }
        }
      }
    }
  • Interpretation of the output:
    A new book was successfully created, returning the book’s id, title, and its associated author object.

Subscriptions Example

Subscriptions enable real-time data. While curl isn’t typically used for real-time operations, the concept is that a client subscribes to an event, like new books being added:

subscription {
  bookAdded {
    id
    title
    author {
      name
    }
  }
}

Whenever a new book is added, the client receives a push notification with the new book data.