From Queries to Subscriptions: Understanding the Basics of GraphQL

ยท

7 min read

Introduction:

GraphQL is a modern query language that allows clients to retrieve only the data they need from an API. It was developed by Facebook and is quickly becoming the go-to choice for building efficient and flexible APIs. In this blog post, we will dive deep into the world of GraphQL and explore how it can help you build better and more efficient APIs for your web applications.

Schema:

A GraphQL schema defines the types and fields of data that are available in the API. The schema is written in a language called GraphQL Schema Definition Language (SDL). The schema is the contract between the client and the server, it defines the types of data that can be queried and the shape of the data that will be returned.

For example, you can define a schema for a User type like this:

type User {
  id: ID!
  name: String
  age: Int
  address: String
}

Queries:

Queries are used to request specific fields from the server. They are written in GraphQL syntax and are sent to the server as a string. Here's an example of a query that requests the name and age fields of a user with an id of 1:

query {
  user(id: 1) {
    name
    age
  }
}

Resolvers:

Resolvers are responsible for fetching the data from the data source and returning it to the client. The resolver functions take in the query as an argument, process it to fetch the requested data and return it.

Here's an example of a simple resolver for the user field in the previous schema:

const resolvers = {
  Query: {
    user: (_, args) => {
      const {id} = args;
      const user = userRepository.getUserById(id);
      return user;
    }
  }
}

In this example, the resolver function retrieves the user data from the database using the id passed in the query and returns an object containing the requested fields.

Mutations:

Mutations are used to make changes to the data on the server. They can be used to create, update, or delete data. Like queries, mutations are written in GraphQL syntax and are sent to the server as a string. Here's an example of a mutation that creates a new user:

mutation {
  createUser(input: { name: "Sherlock Holmes", age: 28, address: "221B Baker Street" }) {
    id
    name
    age
    address
  }
}

Fragments:

Fragments are reusable sets of fields that can be included in multiple queries or mutations. They are defined in the schema and can be used to avoid repeating the same fields in different queries.

Let's say we have a User type in our schema and we have two queries that need to retrieve different fields for a user, but both also need the name and email fields.

Without fragments, our queries would look like this:

query {
  user(id: 1) {
    name
    email
    age
  }
}

query {
  user(id: 2) {
    name
    email
    address
  }
}

As you can see, the name and email fields are repeated in both queries.

With fragments, we can define a fragment for the common fields and include them in both queries like this:

fragment BaseUserInfoFragment on User {
  name
  email
}

query {
  user(id: 1) {
    ...BaseUserInfoFragment
    age
  }
}

query {
  user(id: 2) {
    ...BaseUserInfoFragment
    address
  }
}

As you can see, using fragments allows us to keep our queries organized and easy to read. It also makes it easier to maintain the code since we only need to update the fragment if the fields change, instead of updating every query that uses those fields.

Input Types:

Input types in GraphQL are used to specify the type of input data that can be sent to the server in a mutation operation. Mutations in GraphQL are used to modify data on the server, for example, by creating, updating, or deleting records. Input types are used to define the structure of the data that will be sent in a mutation, in much the same way that types are used to define the structure of the data that will be returned from a query.

Here is an example of a simple input type definition in GraphQL Schema Definition Language (SDL):

input CreateUserInput {
  name: String!
  age: Int!
  email: String!
}

In this example, the input type CreateUserInput is used to specify the structure of the data that will be sent when creating a new user. It requires the fields name, age, and email to be present, and each of these fields must be of a specific type (e.g. String and Int).

When defining a mutation that uses this input type, you would specify the input type in the arguments section of the mutation:

mutation createUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    name
    age
    email
  }
}

In this example, the mutation createUser takes a single argument $input, which is of type CreateUserInput. When the mutation is executed, the client will pass an object that matches the structure of the CreateUserInput type and the server will use this data to create a new user.

Subscriptions:

Subscriptions in GraphQL allow for real-time updates from the server to the client. With subscriptions, the client can receive real-time updates from the server whenever a specific event occurs. This is in contrast to the traditional request-response model in which the client requests data and the server sends a response.

Here's an example of how you might implement a subscription in a GraphQL server using Apollo Server:

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    hello: String
  }

  type Subscription {
    message: String
  }
`;

const messages = [];

const resolvers = {
  Query: {
    hello: () => 'Hello, world!',
  },
  Subscription: {
    message: {
      subscribe: (root, args, { pubsub }) => {
        const channel = Math.random().toString(36).substring(2, 15); // random channel name
        setInterval(() => {
          pubsub.publish(channel, { message: messages[Math.floor(Math.random() * messages.length)] });
        }, 2000);
        return pubsub.asyncIterator(channel);
      },
    },
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`๐Ÿš€ Server ready at ${url}`);
});

In this example, we define a Subscription type in the schema with a single field message. We also define a resolver function for the message field that returns a random message from an array of messages every 2 seconds. The subscribe function uses the pubsub object provided by Apollo Server to publish the message to a random channel. The client can subscribe to this channel using the asyncIterator method of the pubsub object.

To subscribe to the messages from the client, you can use the useSubscription hook from the @apollo/client library:

import React from 'react';
import { useSubscription } from '@apollo/client';
import gql from 'graphql-tag';

const SUBSCRIPTION = gql`
  subscription {
    message
  }
`;

function MessageSubscription() {
  const { data, loading } = useSubscription(SUBSCRIPTION);

  if (loading) return <p>Loading...</p>;

  return <p>{data.message}</p>;
}

export default MessageSubscription;

In this example, we use the useSubscription hook to subscribe to the message field of the Subscription type. The hook returns an object with data and loading properties, which we can use to display the message or a loading indicator.

This is a basic example of how you can use subscriptions in GraphQL, and there are many more advanced concepts and features you can explore, such as implementing custom resolvers, handling errors, and optimizing performance.

Note: For the client-side code, the examples above use Apollo Client. On the server side, the examples use Apollo Server, which is written in JavaScript. Other options for server-side libraries include Sangria for Scala and Graphene for Python. For client-side code, an alternative is Relay.

Conclusion:

One of the benefits of using GraphQL is that it allows for a more flexible and efficient data flow between the client and the server. In a traditional REST API, the client would need to make multiple requests to retrieve all the necessary data, but with GraphQL, the client can request all the necessary data in a single request. This can lead to significant performance improvements, especially for applications with complex data requirements.

Another benefit of GraphQL is that it allows for real-time updates and subscriptions. With GraphQL subscriptions, the client can subscribe to specific events on the server and receive updates in real time. This feature is particularly useful for building real-time applications such as chat apps, social media apps, and more.

In summary, GraphQL is a powerful tool for building efficient and flexible APIs. Its ability to allow clients to fetch only the data they need, handle complex data requirements, and support real-time updates and subscriptions makes it a great choice for building modern web applications. It is worth exploring if you are building or planning to build a web application that requires a robust and efficient API.

ย