Photo by Douglas Lopes on Unsplash
From Queries to Subscriptions: Understanding the Basics of GraphQL
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.