GraphQL API

All about GraphQL


GraphQL is an modern API technology that gives client more control over the data they receive. Also known as a query language for APIs. It lets client apps ask the server for the exact data and shape they need.

Good to know

One of the biggest reason to transition into graphQL is when dealing with different clients such as: mobile, desktop, etc.

Instead of calling many REST endpoints and combining responses together, a GraphQL client sends one structured query and receives a predictable response.

Facebook created GraphQL to fix the most common REST API issues: underfetching and overfetching.

  1. Underfetching

    Traditional REST API often calls multiple endpoints. For example, we may have different endpoint calls for: users, posts, commments, replies, reactions, and more. Each endpoint returns one resource. That pattern is called underfetching. The server responds a single resource for each request, forcing the client to make several round trips.

    Instead of chasing each resource through many endpoints, GraphQL makes a single query request to a single GraphQL endpoint. The server responds with the exact data shape the client asked for.

  2. Overfetching

    In REST API, say the client only needs username field from user. Client still has to call the /users endpoint which returns the entire user object with fields the UI will never read. This is overfetching, the client receives way more data than needed.

    GraphQL fixes this by making fields explicit. The client lists all fields it wants in the GraphQL query. The server returns only those fields, nothing more. This gives the client full control over the shape of the data, eliminating overfetching.

LinkIconBuilding GraphQL APIs

GraphQL has five ideas you need to understand before touching code.

  • Schema: the contract between client and server.
  • Types: the shape of your data.
  • Queries: read data.
  • Mutations: write data.
  • Resolvers: functions that fetch or change data.

Install server dependencies

npm install express graphql express-graphql

Project structure

server/
├─ src/
  ├─ db/
  │  ├─ schema.ts
  │  └─ client.ts
  └─ graphql/
     ├─ schema.ts
     └─ resolvers.ts
└─ index.ts

LinkIconServer

GraphQL schema defines what clients can ask for. Treat this as the public contract.

TypescriptIconserver/graphql/schema.ts
import {
  GraphQLObjectType,
  GraphQLSchema,
  GraphQLString,
  GraphQLInt,
  GraphQLList,
  GraphQLNonNull,
} from 'graphql';
import { users, posts } from '../db/schema';
import { db } from '../db/client';
import { eq } from 'drizzle-orm';
 
const PostType = new GraphQLObjectType({
  name: 'Post',
  fields: {
    id: { type: GraphQLInt },
    title: { type: GraphQLString },
  },
});
 
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: GraphQLInt },
    name: { type: GraphQLString },
    posts: {
      type: new GraphQLList(PostType),
      resolve: async (user) => {
        return db.select().from(posts).where(eq(posts.userId, user.id));
      },
    },
  },
});
 
const RootQuery = new GraphQLObjectType({
  name: 'Query',
  fields: {
    users: {
      type: new GraphQLList(UserType),
      resolve: async () => {
        return db.select().from(users);
      },
    },
  },
});
 
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    createUser: {
      type: UserType,
      args: {
        name: { type: new GraphQLNonNull(GraphQLString) },
      },
      resolve: async (_, { name }) => {
        const [user] = await db
          .insert(users)
          .values({ name })
          .returning();
        return user;
      },
    },
  },
});
 
export const schema = new GraphQLSchema({
  query: RootQuery,
  mutation: Mutation,
});

Let's create a Express server

TypescriptIconserver/index.ts
import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { schema } from './graphql/schema';
 
const app = express();
 
app.use(
  '/graphql',
  graphqlHTTP({
    schema,
    graphiql: true,
  }),
);
 
app.listen(4000, () => {
  console.log('GraphQL server running on http://localhost:4000/graphql');
});

LinkIconClient

Set up client with React

npm install @apollo/client graphql

To use GraphQL in the client, we need to set up Apollo.

TypescriptIconclient/src/apollo.ts
import { ApolloClient, InMemoryCache } from '@apollo/client';
 
export const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql',
  cache: new InMemoryCache(),
});

Then create a context Apollo provider

TypescriptIconclient/src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ApolloProvider } from '@apollo/client';
import { client } from './apollo';
import App from './App';
 
ReactDOM.createRoot(document.getElementById('root')!).render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
);

LinkIconQuery

Here we set all the queries from the client.

TypescriptIconclient/src/queries/users.ts
import { gql } from '@apollo/client';
 
export const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      posts {
        id
        title
      }
    }
  }
`;

Note the shape of the data is nested: users and posts.

In your React component:

import { useQuery } from '@apollo/client';
import { GET_USERS } from './queries/users';
 
export default function App() {
  const { data, loading } = useQuery(GET_USERS);
 
  if (loading) return <p>Loading</p>;
 
  return (
    <ul>
      {data.users.map((user) => (
        <li key={user.id}>
          {user.name}
          <ul>
            {user.posts.map((post) => (
              <li key={post.id}>{post.title}</li>
            ))}
          </ul>
        </li>
      ))}
    </ul>
  );
}

LinkIconMutation

TypescriptIconclient/src/mutations/users.ts
import { gql, useMutation } from '@apollo/client';
 
export const CREATE_USER = gql`
  mutation CreateUser($name: String!) {
    createUser(name: $name) {
      id
      name
    }
  }
`;

In your React app, simply use mutation in a form:

import { useMutation } from '@apollo/client';
import { CREATE_USER } from './mutations/users';
 
const [createUser] = useMutation(CREATE_USER);
createUser({ variables: { name: 'Ana' } });