Learning GraphQL Part 1: Setting up Apollo GraphQL server and React app

How to setup an Apollo GraphQL server with a basic schema and consume the same in a React app with vite

ยท

7 min read

Table of contents

No heading

No headings in the article.

I think of GraphQL as a way to facilitate a smoother server-client communication as compared to REST. No need to fight over API contracts or composite APIs. Client requests what it needs and the backend magically provides. Almost too good to be true.

There however was a metaphorical beast lurking around just beyond the realm of the network chasm: the GraphQL backend. Some context: I have been dabbling with GraphQL on the client side for some time with Hasura and it had been fun all through.

You see, Hasura would auto generate the backend for me and I could just use it. In the real world however, not all places would have an auto generated GraphQL backend. This became true for me around June this year when I joined Atlassian (yes the folks behind Jira, Confluence and the likes).

All the frontend projects had a node BFF layer that acted as an abstraction and sometimes composition layer over the internal business APIs. Post that the BFF exposed a GraphQL endpoint for the frontend to consume. This is where I got introduced with the other side of GraphQL: the resolvers. And like all things difficult, the only way to truly remove the fear of the unknown is to meet it face to face. That is the point of this blog: to document my learning journey.

Now there are different ways of learning something new. One way could be to start at the very fundamentals and build from there. Another approach could be to build on what I knew so far. Both has it's own pros and cons. I chose the 2nd approach.

At work we were using Apollo GraphQL and I went into it's docs. I wanted to quickly setup a React project and talk to the readymade GraphQL backend as per the tutorial on Apollo's website: apollographql.com/docs/react/get-started

I used vitejs to quickly spin up a typescript react project and then installed the dependencies:

  1. @apollo/client
  2. graphql

My main.tsx looked like this:

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

const client = new ApolloClient({
  uri: 'https://flyby-gateway.herokuapp.com/',
  cache: new InMemoryCache(),
});

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);

Next step was to actually fire the network call. This needed change in the App.tsx.

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

const GET_LOCATIONS = gql`
  query GetLocations {
    locations {
      id
      name
      description
      photo
    }
  }
`;

function DisplayLocations() {
  const { loading, error, data } = useQuery(GET_LOCATIONS);

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

  return data.locations.map(({ id, name, description, photo }) => (
    <div key={id}>
      <h3>{name}</h3>
      <img width="400" height="250" alt="location-reference" src={`${photo}`} />
      <br />
      <b>About this location:</b>
      <p>{description}</p>
      <br />
    </div>
  ));
}

export default function App() {
  return (
    <div>
      <h2>My first Apollo app ๐Ÿš€</h2>
      <br/>
      <DisplayLocations />
    </div>
  );
}

Screenshot 2022-09-11 at 9.25.39 AM.png

So far so good. Now to the hard part. Creating our custom GraphQL server.

I headed over to the server side tutorial here: apollographql.com/docs/apollo-server/gettin..

GraphQL provides a way to parse incoming requests and then provide a way to fetch data source for each and every field before returning to the user. These are known as resolvers.

In our client side tutorial, we made the following query:

const GET_LOCATIONS = gql`
  query GetLocations {
    locations {
      id
      name
      description
      photo
    }
  }
`;

Here we can define resolvers for id, name, description and photo individually. While it is possible to run an Apollo GraphQL server alongside express, we will use the batteries included version.

Let's create a new folder and npm init -y inside it.

mkdir server
cd server
npm init -y

Now lets install dependencies:

npm install apollo-server graphql

All good so far.

Everything in GraphQL starts with a type definition. Sadly this is different from typescript types.

For the purpose of the tutorial, we are building an API for querying a list of books. The data source, static or from a database is irrelevant to GraphQL.

Lets create typedefs.ts file and add the following code in it:

import { gql } from "apollo-server";

// A schema is a collection of type definitions (hence "typeDefs")
// that together define the "shape" of queries that are executed against
// your data.
export default gql`
  # Comments in GraphQL strings (such as this one) start with the hash (#) symbol.

  # This "Book" type defines the queryable fields for every book in our data source.
  type Book {
    title: String
    author: String
  }

  # The "Query" type is special: it lists all of the available queries that
  # clients can execute, along with the return type for each. In this
  # case, the "books" query returns an array of zero or more Books (defined above).
  type Query {
    books: [Book]
  }
`;

Next create an src/index.ts file and add the following code inside it:

const books = [
  {
    title: 'The Awakening',
    author: 'Kate Chopin',
  },
  {
    title: 'City of Glass',
    author: 'Paul Auster',
  },
];

// Resolvers define the technique for fetching the types defined in the
// schema. This resolver retrieves books from the "books" array above.
const resolvers = {
  Query: {
    books: () => new Promise(res => {
      setTimeout(() => {
        res(books);
      }, 1000)
    }),
  },
};

Here, books is a static data set. As per our typedef, the only query we can have is books. The same is defined in our resolvers object.

We have returned a promise to simulate async. It can also be a static return. GraphQL does not care and will handle the pending and error states cleanly as we saw with the client side example.

Bringing it all together is our instance of the server:

// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
const server = new ApolloServer({
  typeDefs,
  resolvers,
  csrfPrevention: true,
  cache: 'bounded',
  /**
   * What's up with this embed: true option?
   * These are our recommended settings for using AS;
   * they aren't the defaults in AS3 for backwards-compatibility reasons but
   * will be the defaults in AS4. For production environments, use
   * ApolloServerPluginLandingPageProductionDefault instead.
  **/
  plugins: [
    ApolloServerPluginLandingPageLocalDefault({ embed: true }),
  ],
});

// The `listen` method launches a web server.
server.listen().then(({ url }: { url: string }) => {
  console.log(`๐Ÿš€  Server ready at ${url}`);
});

All that's left is to start it up!

Lets add 2 more dependencies and the start script:

npm i -D nodemon ts-node
"start": "nodemon --exec ts-node src/index.ts --watch",

Screenshot 2022-09-11 at 10.02.41 AM.png ๐ŸŽ‰

Next step would be to actually consume the data. But before that, let's generate typescript types for our schema. Enter [graphql-codegen](https://www.the-guild.dev/graphql/codegen).

I added the dependencies:

  1. graphql-codegen
  2. @graphql-codegen/cli
  3. @graphql-codegen/typescript

And the config: codegen.yaml at the root of the project.

schema: "./src/typedefs.ts" # GraphQL types (input file)
generates:
  ./gql-types.d.ts: # Typescript types (output generated file)
    plugins: # List of needed plugins (installed as devDeps)
      - typescript

And finally, the codegen script:

"generate-gql-types": "graphql-codegen"
npm run generate-gql-types

This would generate a file named gql-types.d.ts as per the config with the following content:

export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
};

export type Book = {
  __typename?: 'Book';
  author?: Maybe<Scalars['String']>;
  title?: Maybe<Scalars['String']>;
};

export type Query = {
  __typename?: 'Query';
  books?: Maybe<Array<Maybe<Book>>>;
};

Here, the Book type can be used in the resolver and the Query type can be consumed by the consuming code: our frontend app.

Lets copy the generated type into our react codebase and make some changes to it's GraphQL client config.

This is how the client config in main.tsx looks now:

const client = new ApolloClient({
  uri: 'http://localhost:4000/',
  cache: new InMemoryCache(),
});

Inside App.tsx lets define a new Books component and it's query counterpart:

const GET_Books = gql`
  query GetBooks {
    books {
      title
      author
    }
  }
`;

function Books() {
  const { loading, error, data } = useQuery<Book>(GET_Books);

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

  return (
    <table>
      <thead>
        <tr>
          <th>Title</th>
          <th>Author</th>
        </tr>
      </thead>
      <tbody>
        {data?.books && data.books.map((book, index) => (
          <tr key={index}>
            <td>{book?.title}</td>
            <td>{book?.author}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Here, note the useQuery hook. We are passing Book as a generic. This is the same type generated by the codegen. This way our code is typed end to end.

Now we can finally consume it inside App.

export default function App() {
  return (
    <div>
      <h2>My first Apollo app ๐Ÿš€</h2>
      <br />
      <Books />
    </div>
  );
}

Screenshot 2022-09-11 at 10.18.41 AM.png ๐ŸŽ‰๐ŸŽ‰

All the code above is open source and is accessible here: github.com/rohanBagchi/graphql-tutorial-notes

ย