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 insidegraphql/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.