Chapter 3: GraphQL Codegen

Full Stack TypeScript

link GraphQL Codegen

By the end of this lesson, developers will be able to:

  • Generate hooks for the frontend client
  • Update data based on queries responses
  • Create data with a mutation hook

Introduction

In this section, we introduce a powerful new tool for automating requests. GraphQL Code Generator will generate code for our mobile application. Let's begin by generating a new hooks file and then displaying data on the mobile app.

Switching Gears

Take a moment, it's time to switch back to React Native.

Heads Up! This lesson's code edits will only affect your React Native project.

link Overview

Before getting started, take a tour of this getting started doc:

Dependencies

To get started with GraphQL Codegen, cd into your app project directory and install these dependencies:

yarn add @apollo/react-common @apollo/react-hooks
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo

Now we can create a new file called codegen.yml. This file acts as a playbook for our codegen rules.

touch codegen.yml

We are going to insert the following snippet to automatically generate a file called graphql-hooks.ts:

overwrite: true
schema: 'http://localhost:4000/graphql'
documents: 'graphql/*.graphql'
generates:
  graphql/graphql-hooks.ts:
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-react-apollo'
    config:
      withHOC: false
      withComponent: false
      withHooks: true

link Define GraphQL

Once we define queries and resolvers, GraphQL Codegen will handle the rest. Let's create a new file called graphql/places.graphql.

mkdir graphql
touch graphql/places.graphql
touch graphql/place.graphql
touch graphql/createPlace.graphql

Insert the following query snippet inside places.graphql:

query Places {
  places {
    id
    title
    description
    imageUrl
    creationDate
  }
}

Insert the following query snippet inside place.graphql:

query Place($id: Float!) {
  place(id: $id) {
    id
    title
    description
    imageUrl
    creationDate
  }
}

Insert the following query snippet inside createPlace.graphql:

mutation CreatePlace(
  $title: String!
  $description: String!
  $imageUrl: String!
) {
  createPlace(
    place: { title: $title, description: $description, imageUrl: $imageUrl }
  ) {
    id
    title
    description
    imageUrl
    creationDate
  }
}

Before running a graphql-codegen command, add it to your package.json scripts.

"scripts": {
    /* ... */
    "generate": "graphql-codegen"
  }

Now we should be able to generate hooks. Start the server in one window and run yarn generate in your mobile app project:

yarn start

In your mobile app project, run:

yarn generate

If the output looks like this, it worked!

Example output:

$ graphql-codegen
  ✔ Parse configuration
  ✔ Generate outputs

We now see a generated file called graphql/graphql-hooks.ts. This file serves as a template for our mobile app's network requests. We will refer back to it momentarily.

link Apollo Client

Now that we have generated our frontend hooks, we can integrate them with React Native. Let's keep the server running and open a separate window to run the app.

Getting started with Apollo Client, we will install the following dependencies:

yarn add apollo-client apollo-cache-inmemory apollo-link-http graphql graphql-tag url 

Up next, we will add the Apollo Client along with a new directory for all GraphQL related code.

mkdir graphql
touch graphql/index.ts

link GraphQL Hooks

  • Confirm the generated graphql/graphql-hook.ts code exists inside graphql/graphql-hooks.ts.

  • Then add the following snippet inside graphql/index.ts:

import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';

const link = createHttpLink({
  uri: 'http://localhost:4000/graphql'
});

export const apolloClient = new ApolloClient({
  cache: new InMemoryCache(),
  link
});

export * from './graphql-hooks';

We are now ready to wire up the Apollo Client inside App.ts.

link Client Context

Modify the top level imports of your App.ts to match the following:

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { DefaultTheme, Provider as PaperProvider } from 'react-native-paper';
import { ApolloProvider } from '@apollo/react-hooks';
import { apolloClient } from './graphql';
// import { Places } from './src/screens';

Make the following changes to your return function:

export default function App() {
  return (
    <PaperProvider theme={theme}>
      <ApolloProvider client={apolloClient}>
        <PaperProvider theme={theme}>
          <View style={styles.container}>
            {/* <Places /> */}
          </View>
        </PaperProvider>
      </ApolloProvider>
    </PaperProvider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff'
  },
  contentContainer: {
    paddingVertical: 40
  }
});

Note that we are about the create the Places screen so it will remain uncommented.

Places Query

We will now create a new component called src/screens/Places.ts. Add the following:

import React from 'react';
import { SafeAreaView, FlatList, Button } from 'react-native';
import { usePlacesQuery } from '../../graphql';
import { CardView } from '../components';

interface Props {}

const Places: React.FC<Props> = () => {
  const { data } = usePlacesQuery();
  return (
    <SafeAreaView>
      <FlatList
        ListFooterComponent={() => (
          <Button
            title="add new place"
            onPress={() => {
              console.log('create place');
            }}
          />
        )}
        data={data && data.places ? data.places : []}
        keyExtractor={item => `${item.id}`}
        renderItem={({ item }) => <CardView {...(item as any)} />}
      />
    </SafeAreaView>
  );
};
export default Places;

We would also like to give it a default import inside src/screens/index.ts:

import Places from './Places';

export { Places };

Wire up the Places screen to App.ts by uncommenting it.

Viola! We should now see a list of places and a new 'Create Place' button.

Up next, we will add our first Mutation hook.

link Mutation Hook

Adding the hook lets us create new places. Lets give it a go inside Places.tsx:

import React from 'react';
import { SafeAreaView, FlatList, Button } from 'react-native';
import {
  usePlacesQuery,
  useCreatePlaceMutation
} from '../../graphql';
import { CardView } from '../components';

interface Props {}

const Places: React.FC<Props> = () => {
  const { data, refetch } = usePlacesQuery();
  const [createPlace] = useCreatePlaceMutation();
  return (
    <SafeAreaView>
      <FlatList
        ListFooterComponent={() => (
          <Button
            title="Add New Place"
            onPress={() => {
              createPlace({
                variables: {
                  title: `Place #${data.places.length + 1}`,
                  description: '',
                  imageUrl: ''
                }
              })
                .then(() => refetch())
                .catch(err => console.log(err));
            }}
          />
        )}
        data={data && data.places ? data.places : []}
        keyExtractor={item => `${item.id}`}
        renderItem={({ item }) => <CardView {...(item as any)} />}
      />
    </SafeAreaView>
  );
};
export default Places;

link Testing Mutation

Re-run the app and try to select the "Add New Place" button.

If a new place appears, success!

WELL DONE!

Wrapping Up

Today we made a TypeGraphQL mutation, with the help of GraphQL Codegen. Up next, we will go deeper into TypeGraphQL with a new User resolver.

format_list_bulleted
help_outline