GraphQL and Apollo 101

GraphQL and Apollo 101

What They Are and Why You Should Use Them.

Have you ever wondered how to fetch data from multiple sources in a fast and efficient way? How to avoid over-fetching or under-fetching data from your backend? How to simplify your data layer and make it more scalable and maintainable? If you answered yes to any of these questions, then you might be interested in learning about GraphQL and Apollo.

GraphQL is a query language for your API that lets you specify exactly what data you want from your backend. It also allows you to define the shape and structure of your data, and how it relates to other data sources. GraphQL makes it easy to fetch data from multiple sources with a single request and avoid the problems of RESTful APIs, such as over-fetching, under-fetching, and multiple round trips.

Apollo is a set of tools and libraries that help you build and use GraphQL APIs. Apollo Client is a library that lets you manage your data and state in your front-end application. Apollo Server is a library that lets you create and run a GraphQL server. Apollo Studio is a platform that lets you explore and test your GraphQL schema and queries. Apollo Federation is a feature that lets you combine multiple GraphQL services into a single data graph.

In this blog post, I will show you how to use GraphQL and Apollo to create a simple web app that fetches data from multiple sources. You will learn the basics of GraphQL and Apollo, and how to set up and use them in your web development project. You will also see how to create a front-end application that uses Apollo Client to interact with a GraphQL server powered by Apollo Server. By the end of this blog post, you will have a better understanding of how GraphQL and Apollo can improve your web development experience.


Mutation

A mutation is an operation that modifies data on your backend. It can be used to create, update, or delete data. A mutation consists of a set of fields that specify what data you want to modify and what data you want to return from your backend. A mutation can also include arguments, variables, aliases, fragments, directives, and other features to customize your request.

A mutation is written using the GraphQL query language, which is the same syntax used for queries. Here is an example of a mutation that creates a new post and returns its id and title:

mutation {
  createPost(title: "Hello World", content: "This is my first post", authorId: "1") {
    id
    title
  }
}

In this mutation, we have specified the name of the operation (mutation) and the name of the field (createPost) that corresponds to the operation defined in our schema. We have also passed on some arguments (title, content, and authorId) to the field to specify the data we want to create. Inside the curly braces ({}), we have listed the fields (id and title) that we want to get from the newly created post object.

The result of this mutation would look something like this:

{
  "data": {
    "createPost": {
      "id": "10",
      "title": "Hello World"
    }
  }
}

The result is a JSON object that contains a property called data, which holds the data requested by our mutation. The data object has a property called createPost, which matches the name of our field in our mutation. The value of this property is another object that contains the properties id and title, which matches the fields in our mutation. The values of these properties are the actual data created on our backend.

Subscription

A subscription is an operation that establishes a real-time connection between your client and your server. It can be used to receive live updates from your backend when something changes. A subscription consists of a set of fields that specify what data you want to subscribe to and what data you want to receive from your backend. A subscription can also include arguments, variables, aliases, fragments, directives, and other features to customize your request.

A subscription is written using the GraphQL query language, which is the same syntax used for queries and mutations. Here is an example of a subscription that listens for new posts and returns their id and title:

subscription {
  newPost {
    id
    title
  }
}

In this subscription, we have specified the name of the operation (subscription) and the name of the field (newPost) that corresponds to the operation defined in our schema. Inside the curly braces ({}), we have listed the fields (id and title) that we want to get from each new post object.

The result of this subscription would look something like this:

{
  "data": {
    "newPost": {
      "id": "11",
      "title": "Hello GraphQL"
    }
  }
}

The result is a JSON object that contains a property called data, which holds the data requested by our subscription. The data object has a property called newPost, which matches the name of our field in our subscription. The value of this property is another object that contains the properties id and title, which matches the fields in our subscription. The values of these properties are the actual data updated on our backend.

Unlike queries and mutations, subscriptions are not executed once and return a single result. Instead, subscriptions are executed continuously and return multiple results over time. Each time something changes on your backend that matches your subscription criteria, you will receive a new result with the updated data.

Resolver

A resolver is a function that tells your GraphQL server how to fetch or modify data for a specific field. It takes some arguments as input, such as the parent object, query arguments, the context object, and info object, and returns some data as output, such as scalar value, object value, list value, or promise value.

A resolver is usually defined on your server-side code using a programming language such as JavaScript, Python, Ruby, or Java. You can use any logic or library you want inside your resolver function to access or manipulate your data sources, such as databases, APIs, files, or caches.

Here is an example of a resolver function for the getUser field in JavaScript:


const resolvers = {
  Query: {
    getUser: async (parent, args, context, info) => {
      // parent is the parent object of the field, which is null for root fields
      // args is the object that contains the query arguments
      // context is the object that contains some shared information across resolvers
      // info is the object that contains some metadata about the query execution
      // use a try-catch block to handle errors
      try {
        // use the context object to access the database connection
        const db = context.db;
        // use the args object to get the id argument
        const id = args.id;
        // use the db object to query the user table by id
        const user = await db.query('SELECT * FROM user WHERE id = $1', [id]);
        // return the user object if found, or null if not found
        return user || null;
      } catch (error) {
        // throw an error if something goes wrong
        throw new Error(error.message);
      }
    }
  }
};

In this resolver function, we have used the async/await syntax to handle asynchronous operations. We have also used a try-catch block to handle errors. We have used the context object to access the database connection, which we have assumed to be a PostgreSQL client. We have used the args object to get the id argument, and used it to query the user table by id. We have returned the user object if found, or null if not found. We have thrown an error if something goes wrong.

Type system

GraphQL has a powerful and expressive type system that defines the valid types and operations in your API. It ensures that your data is consistent and reliable across your backend and frontend. It also provides some features that make your API more flexible and dynamic, such as interfaces, unions, enums, scalars, and input types.

An interface is a type that defines a common set of fields that can be shared by other types. It allows you to create types that can represent different kinds of objects with similar fields. For example, you can create an interface called Node that has a field called id, and then use it by other types such as User and Post.

A union is a type that can represent one of several possible types. It allows you to create types that can represent different kinds of objects without any common fields. For example, you can create a union type called SearchResult that can be either a User or a Post.

An enum is a type that can represent one of a fixed number of possible values. It allows you to create types that can represent different kinds of options or choices. For example, you can create an enum type called Role that can be either ADMIN, EDITOR, or USER.

A scalar is a type that can represent a basic value, such as a string, number, boolean, or null. GraphQL has some built-in scalar types, such as String, Int, Float, Boolean, and ID. You can also create your own custom scalar types to represent other kinds of values, such as dates, timestamps, colors, or coordinates.

An input type is a type that can be used as an argument for queries and mutations. It allows you to create complex types that can represent different kinds of inputs or parameters. For example, you can create an input type called PostInput has fields such as title, content, and authorId.

Here is an example of how to use these types of system features in your schema:

interface Node {
  id: ID!
}

type User implements Node {
  id: ID!
  name: String!
  email: String!
  role: Role!
  posts: [Post!]!
}

type Post implements Node {
  id: ID!
  title: String!
  content: String!
  author: User!
}

union SearchResult = User | Post

enum Role {
  ADMIN
  EDITOR
  USER
}

scalar Date

input PostInput {
  title: String!
  content: String!
  authorId: ID!
}

In this schema, we have created an interface (Node), a union (SearchResult), an enum (Role), a scalar (Date), and an input type (PostInput). We have also modified our existing types (User and Post) to use the interface (Node). These features make our schema more adaptable and powerful.


Apollo basics

Apollo is a collection of tools and libraries that help you build and use GraphQL APIs. Apollo has four main components: Apollo Client, Apollo Server, Apollo Studio, and Apollo Federation. In this section, I will explain the main features of each component and how to set up and use them with GraphQL.

Apollo Client

Apollo Client is a library that lets you manage your data and state in your front-end application. It allows you to fetch data from your GraphQL server using queries and mutations, and update your UI automatically with the results. It also provides features such as caching, optimistic UI, error handling, pagination, subscriptions, and more.

To use Apollo Client in your front-end application, you need to install it using npm or yarn:

npm install @apollo/client
# or
yarn add @apollo/client

You also need to import it in your code and create an instance of it:

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql', // the URL of your GraphQL server
  cache: new InMemoryCache(), // the cache object that stores your data
});

You can then use the client object to perform queries and mutations using the query and mutate methods:

// perform a query using the client.query method
client.query({
  query: gql`
    query {
      getUser(id: "1") {
        name
        email
      }
    }
  `,
})
.then(result => console.log(result.data))
.catch(error => console.error(error));

// perform a mutation using the client.mutate method
client.mutate({
  mutation: gql`
    mutation {
      createPost(title: "Hello World", content: "This is my first post", authorId: "1") {
        id
        title
      }
    }
  `,
})
.then(result => console.log(result.data))
.catch(error => console.error(error));

You can also use the useQuery and mutation hooks to perform queries and mutations in your React components:

import { useQuery, useMutation, gql } from '@apollo/client';

// define the query and mutation using the gql tag
const GET_USER = gql`
  query {
    getUser(id: "1") {
      name
      email
    }
  }
`;

const CREATE_POST = gql`
  mutation {
    createPost(title: "Hello World", content: "This is my first post", authorId: "1") {
      id
      title
    }
  }
`;

// use the useQuery hook to fetch data from the server
function User() {
  const { loading, error, data } = useQuery(GET_USER);

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

  return (
    <div>
      <h1>User</h1>
      <p>Name: {data.getUser.name}</p>
      <p>Email: {data.getUser.email}</p>
    </div>
  );
}

// use the useMutation hook to modify data on the server
function PostForm() {
  const [createPost, { data }] = useMutation(CREATE_POST);

  return (
    <div>
      <h1>Create Post</h1>
      <form onSubmit={e => {
        e.preventDefault();
        createPost();
      }}>
        <button type="submit">Submit</button>
      </form>
      {data && (
        <p>Post created with id: {data.createPost.id}</p>
      )}
    </div>
  );
}

Apollo Server

Apollo Server is a library that lets you create and run a GraphQL server. It allows you to define your schema and resolvers, and connect them to your data sources. It also provides features such as validation, error handling, tracing, caching, subscriptions, and more.

To use Apollo Server in your back-end application, you need to install it using npm or yarn:

npm install apollo-server graphql
# or
yarn add apollo-server graphql

You also need to import it in your code and create an instance of it:

import { ApolloServer } from 'apollo-server';

const server = new ApolloServer({
  typeDefs: ..., // the schema definition using SDL
  resolvers: ..., // the resolver functions for each field
});

You can then start the server using the listen method:

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

You can then use tools like GraphQL Playground or Apollo Studio to explore and test your GraphQL API.

Apollo Studio

Apollo Studio is a platform that lets you explore and test your GraphQL schema and queries. It also provides features such as schema registry, schema validation, schema change notifications, performance monitoring, usage analytics, and more.

To use Apollo Studio, you need to create an account on the Apollo website and get an API key. You also need to install the Apollo CLI tool using npm or yarn:

npm install -g apollo
# or
yarn global add apollo

You also need to configure your Apollo project using the apollo.config.js file:

module.exports = {
  service: {
    name: 'my-service', // the name of your service
    localSchemaFile: './schema.graphql', // the path to your schema file
  },
};

You can then use the apollo CLI tool to push your schema to Apollo Studio using the service: push command:

apollo service:push --key=<your-api-key>

You can also use the Apollo CLI tool to check your schema for breaking changes using the service: check command:

apollo service:check --key=<your-api-key>

You can then access Apollo Studio from your browser and use it to explore and test your GraphQL API.

Apollo Federation

Apollo Federation is a feature that lets you combine multiple GraphQL services into a single data graph. It allows you to split your schema and resolvers into different services based on domains or teams, and then stitch them together using a gateway service. It also provides features such as query planning, schema composition, entity resolution, and more.

To use Apollo Federation, you need to install some additional packages using npm or yarn:

npm install @apollo/federation @apollo/gateway
# or
yarn add @apollo/federation @apollo/gateway

You also need to import them in your code and use them to create federated services and a gateway service:

import { ApolloServer } from 'apollo-server';
import { ApolloGateway } from '@apollo/gateway';
import { buildFederatedSchema } from '@apollo/federation';

// create a federated service for users
const userService = new ApolloServer({
  schema: buildFederatedSchema([
    {
      typeDefs: gql`
        type User @key(fields: "id") {
          id: ID!
          name: String!
          email: String!
        }

        extend type Query {
          getUser(id: ID!): User
        }
      `,
      resolvers: {
        Query: {
          getUser: (parent, args, context, info) => {
            // fetch user from database
          },
        },
        User: {
          __resolveReference(user, context, info) {
            // resolve user reference from other services
          },
        },
      },
    },
  ]),
});

// create a federated service for posts
const postService = new ApolloServer({
  schema: buildFederatedSchema([
    {
      typeDefs: gql`
        type Post @key(fields: "id") {
          id: ID!
          title: String!
          content: String!
          author: User!
        }

        extend type User @key(fields: "id") {
          id: ID! @external
          posts: [Post!]!
        }

        extend type Query {
          getPostsByUser(userId: ID!): [Post!]!
        }
      `,
      resolvers: {
        Query: {
          getPostsByUser: (parent, args, context, info) => {
            // fetch posts by user id from database
          },
        },
        Post: {
          __resolveReference(post, context, info) {
            // resolve post reference from other services
          },
          author(post) {
            return { __typename: 'User', id: post.authorId };
          },
        },
        User: {
          posts(user) {
            return { __typename: 'Post', userId: user.id };
          },
        },
      },
    },
  ]),
});

// create a gateway service that combines the federated services
const gateway = new ApolloGateway({
  serviceList: [
    { name: 'users', url: 'http://localhost:4001/graphql' }, // the URL of the user service
    { name: ‘posts’, url: ‘http://localhost:4002/graphql’ }, // the URL of the post service ], });
// create a server that runs the gateway service const server = new ApolloServer({ gateway, subscriptions: false, // disable subscriptions for the gateway });

// start the server server.listen().then(({ url }) => { console.log(Gateway ready at ${url}); });

You can then use tools like GraphQL Playground or Apollo Studio to explore and test your combined GrapphQL API.

I hope this section helps you understand Apollo basics.

Building a web app with GraphQL and Apollo

In this section, I will show you how to create a simple web app that uses GraphQL and Apollo to fetch data from multiple sources. You will learn how to create a front-end application that uses Apollo Client to interact with a GraphQL server powered by Apollo Server.

Prerequisites

To follow along with this tutorial, you will need:

  • Node.js and npm or yarn installed on your machine

  • A code editor of your choice

  • Basic knowledge of JavaScript, HTML, and CSS

  • A basic understanding of GraphQL and Apollo

Step 1: Set up the project

First, create a new folder for your project and navigate to it:

mkdir graphql-app
cd graphql-app

Then, initialize a new npm or yarn project and install the dependencies:

npm init -y
# or
yarn init -y

npm install @apollo/client apollo-server graphql react react-dom
# or
yarn add @apollo/client apollo-server graphql react react-dom

Next, create two subfolders for your front-end and back-end code:

mkdir client server

Finally, create an index.html file in the client folder and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>GraphQL App</title>
</head>
<body>
  <div id="root"></div>
  <script src="./index.js"></script>
</body>
</html>

This is the HTML template for your front-end application. It has a div element with an id of the root, where your React app will be rendered. It also has a script tag that links to your JavaScript file.

Step 2: Create the schema and resolvers

Next, create an index.js file in the server folder and add the following code:

import { ApolloServer } from 'apollo-server';
import { buildFederatedSchema } from '@apollo/federation';

// define the schema using SDL
const typeDefs = `
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
  }

  type Query {
    getUser(id: ID!): User
    getPostsByUser(userId: ID!): [Post!]!
  }
`;

// define the resolvers for each field
const resolvers = {
  Query: {
    getUser: (parent, args, context, info) => {
      // fetch user from database
    },
    getPostsByUser: (parent, args, context, info) => {
      // fetch posts by user id from database
    },
  },
};

// create a new Apollo server instance
const server = new ApolloServer({
  schema: buildFederatedSchema([
    {
      typeDefs,
      resolvers,
    },
  ]),
});

// start the server
server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);
});

This is the code for your back-end application. It has three parts:

  • The schema definition using SDL. It defines the types and operations available in your API.

  • The resolver functions for each field. They tell your server how to fetch or modify data for each field.

  • The Apollo server instance creation and start. It creates and runs a GraphQL server using your schema and resolvers.

For this tutorial, we will use some mock data as our database. You can replace the database logic with your data source in your real project.

Add the following code at the top of your index.js file in the server folder:

// mock data for users and posts
const users = [
  {
    id: '1',
    name: 'Alice',
    email: 'alice@example.com',
  },
  {
    id: '2',
    name: 'Bob',
    email: 'bob@example.com',
  },
];

const posts = [
  {
    id: '1',
    title: 'Hello World',
    content: 'This is my first post',
    authorId: '1',
  },
  {
    id: '2',
    title: 'Hello GraphQL', 
    content: 'This is my second post',
    authorId: '1',
   }, 
   { id: ‘3’, title: ‘Hello Apollo’, 
     content: ‘This is my third post’,     
     authorId: ‘2’, }, ];

   // update the resolver functions to use the mock               data 
const resolvers = {
  Query: {
    getUser: (parent, args, context, info) => {
      // find and return the user that matches the id                     
      return users.find(user => user.id == args.id); 
    },
    getPostsByUser: (parent, args, context, info) => {
      // filter and return the posts that match the  userId argument
      return posts.filter(post => post.authorId 
      === args.userId);
    },
  },
    User: { posts: (user, args, context, info) => { 
    // filter and return the posts that belong to the user object 
    return posts.filter(post => post.authorId 
    === user.id); }, },
    Post: { author: (post, args, context, info) => { // find and return the user that matches the authorId of the post object 
    return users.find(user => user.id ===   post.authorId); 
      }, 
    },
};

// create a new Apollo server instance
const server = new ApolloServer({
  schema: buildFederatedSchema([
    {
      typeDefs,
      resolvers,
    },
  ]),
});

// start the server
server.listen().then(({ url }) => {
  console.log(`Server ready at ${url}`);

This completes the code for your backend application. You can now run it using the following command:

node server/index.js

You should see a message like this:

Server ready at http://localhost:4000/

You can then use tools like GraghQL playground or Apollo Studio to explore and test your GrapgQL API.

I hope this step helps you create your backend-end application. If you need more assistance please let me know.

Step 3: Create the front-end application

Next, create an index.js file in the client folder and add the following code:

import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

// create a new Apollo client instance
const client = new ApolloClient({
  uri: 'http://localhost:4000/', // the URL of your GraphQL server
  cache: new InMemoryCache(), // the cache object that stores your data
});

// create a React component for your app
function App() {
  return (
    <div>
      <h1>GraphQL App</h1>
      {/* add your components here */}
    </div>
  );
}

// wrap your app with the Apollo provider
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById('root')
);

This is the code for your front-end application. It has three parts:

  • The Apollo client instance creation. It creates and configures a client object that connects to your GraphQL server and manages your data.

  • The React component for your app. It defines the UI and logic for your app using JSX and JavaScript.

  • The Apollo provider wrapping. It wraps your app with a provider component that passes the client object to your app and its children.

For this tutorial, we will use some simple components to display the data from our GraphQL API. You can use any UI library or framework you prefer, such as Material UI, Bootstrap, or Tailwind CSS.

Add the following code at the bottom of your index.js file in the client folder:

import { useQuery, gql } from '@apollo/client';

// define the query using the gql tag
const GET_USER = gql`
  query {
    getUser(id: "1") {
      name
      email
      posts {
        id
        title
      }
    }
  }
`;

// create a React component that uses the useQuery hook to fetch data from the server
function User() {
  const { loading, error, data } = useQuery(GET_USER);

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

  return (
    <div>
      <h2>User</h2>
      <p>Name: {data.getUser.name}</p>
      <p>Email: {data.getUser.email}</p>
      <h3>Posts</h3>
      <ul>
        {data.getUser.posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

// add the User component to your app component
function App() {
  return (
    <div>
      <h1>GraphQL App</h1>
      <User />
    </div>
  );
}

This completes the code for your front-end application. You can now run it using a simple web server such as serve:

npx serve client

You should see a message like this:

Serving!                                    

- Local:            http://localhost:5000   
- On Your Network:  http://192.168.0.1:5000 

Copied local address to clipboard!

You can then open your browser and go to http://localhost:5000 to see your app in action.


I hope this step helps you create your front-end application. If you need more assistance, please let me know.

Conclusion

In this blog post, we have learned about GraphQL and Apollo, two powerful technologies for web development. We have seen how GraphQL enables us to fetch data declaratively from a single endpoint, using a type system and a query language that describes the shape and structure of the data we want. We have also seen how Apollo provides us with a set of tools and libraries to manage the state of our data, both on the client and the server side, using intelligent caching, local state management, and federation. We have also built a simple web app that uses GraphQL and Apollo to fetch data from multiple sources and display it in a user-friendly interface.

By using GraphQL and Apollo, we can benefit from a consistent, predictable, and performant API that can handle complex data requirements across different platforms and devices. We can also ship features faster and write less code by leveraging Apollo’s features and integrations. GraphQL and Apollo are not only useful for web development but also for mobile development, as they provide native support for iOS and Android.

If you want to learn more about GraphQL and Apollo, you can check out the following resources:

Thank you for reading this blog post, and happy coding!