Graph Query Language, also known as GraphQL, is a powerful query language and runtime for APIs. By using GraphQL, clients can request exactly the data they need. Facebook developed GraphQL in 2012, and it was open-sourced in 2015.
What makes GraphQL special is its efficiency and flexibility—it allows clients to define the exact data they need and retrieve it with a single request, unlike REST, which often requires multiple requests.

Before diving into GraphQL, let's first understand how it differs from REST, the API style most developers are familiar with.

Comparison with REST APIs

The fundamental difference between REST and GraphQL is that REST APIs are built around resources and their representations, using multiple endpoints. In contrast, GraphQL is a query language that operates through a single endpoint to perform various operations.

REST API Characteristics

Multiple Endpoints: REST APIs typically expose multiple endpoints, each corresponding to a specific resource or task. When a client needs data from different resources, it must send multiple requests.

Fixed Datasets: REST APIs return a predefined set of data, regardless of what the client actually needs. Clients cannot request specific fields, which can lead to over-fetching and slower response times.

GraphQL Characteristics

Single Endpoint and Flexible Queries: GraphQL uses one endpoint for all operations. Clients can request precisely the data they need, avoiding redundant or irrelevant data.

Customizable Responses: Clients define the exact structure of the required data and receive only that. Even if data spans multiple resources, a single query can retrieve it efficiently.

Schema and Types: GraphQL uses a strong type system to define the API's structure. It clearly outlines types and their relationships, serving as a contract between client and server.

Core Concepts

Schema

The schema defines what data clients can query and the relationships between different types. It acts as a contract that ensures consistency and structure in the API.

Schema Image

Types

A schema is composed of various types, each representing the shape and structure of the data:

  • Object Types: Define entities with fields (e.g., Author, Book).
  • Scalar Types: Primitive data types like String, Int, Float, Boolean.
  • Enum Types: Represent a predefined set of values.
  • Interface Types: Define a set of fields that multiple types can implement.
  • Union Types: Represent a type that can be one of several different types.

Fields

Fields represent values that can be queried or mutated. For example, an Author type may have fields like id and name. Each field can return various data types and may have a resolver function to fetch its value.

Fields Image

Arguments

GraphQL allows arguments to be passed to fields, enabling clients to filter or sort the data. These arguments can be required, optional, or have default values.

Arguments Image

Queries

Queries are used to fetch data. Clients can request specific fields, reducing the amount of data transferred. This differs from REST, where responses are defined by the server.

Queries Image

Mutations

Mutations are used to modify data—create, update, or delete resources. Like queries, they allow clients to specify what data should be returned after the operation.

Mutations Image

Resolvers

Resolvers are functions that return the data for a field. Each field can have a custom resolver that defines how its value is retrieved or computed.

Resolvers Image

Advantages of GraphQL

Efficient Data Fetching

GraphQL minimizes over-fetching and under-fetching by letting clients request only the data they need, improving performance and reducing bandwidth usage.

Flexibility in Queries

Clients can query multiple resources in one request and specify exactly which fields they need. This supports rapid frontend development without backend changes.

Strongly Typed System

The schema’s type system ensures that the data returned is consistent and helps prevent type-related errors.

Real-Time Data with Subscriptions

GraphQL supports subscriptions, enabling real-time updates for applications like chat, notifications, and live dashboards.

Evolution Without Versioning

GraphQL APIs can evolve by adding new fields without breaking existing clients, eliminating the need for versioning.

Common Use Cases

Single Endpoint for Multiple Data Sources

GraphQL can combine data from various services into one endpoint, simplifying client-side data management.

Real-Time Applications

With subscriptions, GraphQL is ideal for applications requiring real-time updates like messaging or live scores.

Microservices Architecture

In a microservices setup, GraphQL can act as a unified gateway, aggregating responses from different services into a single API.

Client-Driven Development

Frontend developers can define queries independently, speeding up development and reducing dependencies on backend teams.

Mobile and Low-Bandwidth Applications

By transferring only the necessary data, GraphQL conserves bandwidth and improves performance on slow connections.

Let’s Code a Simple GraphQL Application

Here’s a simple GraphQL server using Node.js and Express to demonstrate the core concepts:

npm init -y
npm install express express-graphql graphql


const express = require('express');
const {graphqlHTTP} = require('express-graphql');
const {
 GraphQLSchema,
 GraphQLObjectType,
 GraphQLString,
 GraphQLInt,
 GraphQLNonNull,
 GraphQLList
} = require('graphql')
//
const app = express();

// TEST DATA
const authors = [
 { id: 1, name: 'J. K. Rowling' },
 { id: 2, name: 'J. R. R. Tolkien' },
 { id: 3, name: 'Brent Weeks' }
]
const books = [
 { id: 1, name: 'Harry Potter and the Chamber of Secrets', authorId: 1 },
 { id: 2, name: 'Harry Potter and the Prisoner of Azkaban', authorId: 1 },
 { id: 3, name: 'Harry Potter and the Goblet of Fire', authorId: 1 },
 { id: 4, name: 'The Fellowship of the Ring', authorId: 2 },
 { id: 5, name: 'The Two Towers', authorId: 2 },
 { id: 6, name: 'The Return of the King', authorId: 2 },
 { id: 7, name: 'The Way of Shadows', authorId: 3 },
 { id: 8, name: 'Beyond the Shadows', authorId: 3 }
]
//
// Define the Type [AuthorType]
const AuthorType = new GraphQLObjectType({
 name: 'Author',
 description: 'This represents an author of a book',
 // Define the fields to the AuthorType
 fields: () => ({
   id: {
     type: GraphQLNonNull(GraphQLInt),
     resolve: (author) => author.id
   },
   name: {
     type: GraphQLNonNull(GraphQLString),
     resolve: (author) => author.name
   },
 }),
});
//
const BookType = new GraphQLObjectType({
 name: 'Book',
 description: 'This represents a book written by an author',
 fields: () => ({
   id: {
     type: GraphQLNonNull(GraphQLInt),
     resolve: (book) => book.id
   },
   name: {
     type: GraphQLNonNull(GraphQLString),
     resolve: (book) => book.name
   },
   authorId: {
     type: GraphQLNonNull(GraphQLInt),
     resolve: (book) => book.authorId
   },
   author: {
     type: AuthorType,
     // `book` is the parent object, we can get the authorId from the book object
     // Inside the BookType, we have a field called author, which is of type AuthorType.
     // Therefore the `AuthorType` book is the parent object, so we can get the authorId from the book object.
     resolve: (book) => {
       return authors.find(author => author.id === book.authorId)
     }
   }
 }),
});
//
// make a Root Query to get and set the different kind of data..
const RootQueryType = new GraphQLObjectType({
 name: 'Query',
 description: 'Root Query',
 fields: () => ({
   // Get Books ...
   books: {
     type: new GraphQLList(BookType),
     description: 'A list of Books',
     resolve: () => books // for now we just return the books array :)
   },
   // Get Authors ...
   authors: {
     type: new GraphQLList(AuthorType),
     description: 'A list of Authors',
     resolve: () => authors // for now we just return the authors array :)
   },
   // Get Single book ...
   book: {
     type: BookType,
     description: 'A single book',
     args: { // Define the allowed arguments for the query.
       id: { type: GraphQLNonNull(GraphQLInt) }, // `id` is a Mandatory field, If we don't pass the id, it will throw an error.
       name: { type: GraphQLString } // `name` is an optional field, If we pass the name, it will filter the books based on the name.
     },
     resolve: (parent, args) => books.find(book => book.id === args.id)
   },
   // Get Single author ...
   author: {
     type: AuthorType,
     description: 'A single author',
     args: { // this is how we can pass the arguments to the query ...
       id: { type: GraphQLInt }
     },
     resolve: (parent, args) => {
       /*
         In GraphQL, We do not have the option to send status codes like 404, 500 etc. So, we have to throw an error to handle the status code.
         there in the Error we can tell what is the error message and what is the status code (if needed).
       */
       if (!args.id)
         throw new Error('Please provide the author id');
       //
       const author = authors.find(author => author.id === args.id);
       if (!author)
         throw new Error(`Author who has the id ${args.id} is not found`);
       //
       return author;
     }
   }
 }),
});
//
// Mutation [Modify the data]
// In the REST API, we use [POST], [PUT], [DELETE] to modify the data.
// In GraphQL, we use Mutation to modify the data.
const RootMutationType = new GraphQLObjectType({
 name: 'Mutation',
 description: 'Root Mutation',
 fields: () => ({
   addBook: {
     type: BookType,
     description: 'Add a book',
     args: {
       name: {
         type: GraphQLNonNull(GraphQLString),
       },
       authorId: {
         type: GraphQLNonNull(GraphQLInt),
       }
     },
     // resolve is used to add the book to the database.
     resolve: (parent, args) => {
       const book = { id: books.length + 1, name: args.name, authorId: args.authorId };
       books.push(book);
       return book;
     }
   }
 }),
});
//
// [Create a schema]
// Schema is used to define the structure of the data that we can query. It is like a blueprint of the data.
// Single schema can have multiple queries and mutations. Typically we have only one query and one mutation with one schema for the application.
const schema = new GraphQLSchema({
 query: RootQueryType, // [Query] is used to get the data from the database
 mutation: RootMutationType // [Mutation] is used to update the data in the database [CRUD]
});
//
app.use('/graphql', graphqlHTTP({
 schema: schema, // register the schema with the graphql
 graphiql: true // graphiql is a tool to test the graphql queries. developers can test the queries in the browser.
}));
//
// Start the server
app.listen(5050, () => {
 console.log(`Server is running on http://localhost:5050/graphql`);
});

Once the server is running, you can test queries in the browser using GraphiQL.

GraphiQL Playground

Popular GraphQL Libraries

Node.js

  • Apollo Server: Robust and production-ready, integrates with Express, Koa, and others. Supports schema stitching and monitoring.
  • Express-GraphQL: Lightweight and simple for quick setups with basic features.
  • GraphQL Yoga: Built on Apollo, with support for subscriptions and WebSocket.

Python

  • Graphene: Popular library that works well with Django and Flask.
  • Ariadne: Schema-first development using GraphQL syntax.
  • Strawberry: Code-first approach using Python type hints.

Best Practices and Considerations

Schema Design

  • Keep it simple and intuitive.
  • Use descriptive names for types and fields.
  • Modularize schemas for maintainability.

Error Handling

  • Provide meaningful error messages with context.
  • Use standardized error codes.

Security

  • Limit query complexity to prevent performance issues.
  • Implement robust authentication and authorization.
  • Sanitize inputs to avoid injection attacks.

Performance

  • Use DataLoader for batching and caching.
  • Optimize resolvers for minimal overhead.
  • Implement server-side caching.

Documentation and Tooling

  • Document your schema using tools like GraphiQL or GraphQL Playground.

Pagination

  • Implement pagination to handle large datasets efficiently.

Conclusion

GraphQL is a powerful alternative to REST, offering flexibility, precision, and efficiency. By letting clients define their data needs, it solves common challenges like over-fetching and under-fetching.

Key Takeaways

  1. Efficient Data Fetching: Minimize data transfer and improve performance.
  2. Flexible Queries: Retrieve only the data you need.
  3. Strong Typing: Enforces structure and reduces errors.
  4. Real-Time Features: Support for live data updates via subscriptions.
  5. API Evolution: Add new features without breaking clients.
  6. Wide Adoption: Used by major tech companies like Facebook, GitHub, and Shopify.

Thank you!

A Bandara
Senior Software Engineer
"CODIMITE" Would Like To Send You Notifications
Our notifications keep you updated with the latest articles and news. Would you like to receive these notifications and stay connected ?
Not Now
Yes Please