DEV Community

Cover image for Fullstack, Type safe application with React and GraphQL codegen
Achraf
Achraf

Posted on • Originally published at blog.escape.tech

Fullstack, Type safe application with React and GraphQL codegen

There are 2 approaches to defining your schema in GraphQL: schema-first or code-first.

In schema-first, you write .graphql (or .gql) files, while in code-first you write resolver-like types in Javascript (or Typescript).

Now, if you're using Typescript, you might find yourself having to write types again for other purposed, your resolvers for example. That can quickly become a problem, not only because it feels like a waste of time, but also because it makes it that much harder to maintain. If your codebase and your schema grow in complexity, and you have a whole team working on it, a small type definition update can cause a huge mess!

If we look at a fullstack Typescript application, we have to duplicate our type definition at least 3 times:

  1. in the schema file
  2. in the backend resolvers
  3. in the frontend for the GraphQL client
  4. BONUS: for the ORM

Enters GraphQL code generator

GraphQL code generator is the solution to that problem: you write your schema file and the rest gets generated automatically!

Now let's see how it actually works 👇

This was originally posted on blog.escape.tech

Example: Building a (over-engineered) blog

I know you love using overly complicated tools to build your blog so let's just do that cause why not 🤷‍♂️

Here are the graph relations:

As mentioned above, with this stack we would normally have to:

  1. write our type definitions in a schema file
  2. write types for our backend resolvers
  3. write model definitions for our ORM (using Prisma in this example)
  4. write types for React Query on the frontend

Phew, that's a lot of effort!

Now imagine if in 4 months we decide to add tags to our posts. We would have to go through the same 4 steps to update the types!

But with GraphQL codegen, we have one single source of truth: the schema file!

Alright, enough teasing, let's jump into the code!

Backend with Express and Express GraphQL

If you start from scratch, you can simply install Express, Express GraphQL and Typescript (+ some other utilities):

npm install express express-graphql @graphql-tools/schema cors import-graphql-node
npm install -D @types/express
Enter fullscreen mode Exit fullscreen mode

Then we can very easily setup the server:

import "import-graphql-node"
import express from "express"
import {GraphQLHTTP} from "express-graphql"
import cors from "cors"
import {makeExecutableSchema} from "@graphql-tools/schema"
import * as typeDefs from "./schema.graphql"


const app = express()
app.use(cors())

const schema = makeExecutableSchema({ typeDefs })

app.use("/", GraphQLHTTP({
  context: {db},
  schema: schema,
  graphql: true
}))
Enter fullscreen mode Exit fullscreen mode

Note here that I'm using import-graphql-node to import .graphql files.

Checkout the repo for more details.

Frontend with React and React Query

We can bootstrap a React and Typescript project very easily with the Create React App boilerplate:

npx create-react-app client --template typescript
Enter fullscreen mode Exit fullscreen mode

Next, let's add React Query:

npm install react-query
Enter fullscreen mode Exit fullscreen mode

and set it up:

import "./style.css"
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
import {QueryClient, QueryClientProvider} from "react-query"

const client = new QueryClient()

ReactDOM.render(
  <React.StrictMode>
    <QueryClientProvider client={client}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
  document.getElementById("root")
)
Enter fullscreen mode Exit fullscreen mode

Setting up GraphQL codegen

Setting up GraphQL codegen is super easy! First, install the CLI:

npm install -D @graphql-codegen/cli
Enter fullscreen mode Exit fullscreen mode

Then launch the initialisation proces:

npx graphql-codegen init
Enter fullscreen mode Exit fullscreen mode

This will prompt a series of questions to set it up for your needs. It's not super important as it's very easy to update the configuration later.

Here is (approximately) the config file that you'll end up with:

Let's go over each field to explain what it does and configure them exactly how we need it.

Schema

This should point to your schema definition. By default, it uses your GraphQL endpoint, but in general it's easier to put the path to your actual schema file:

schema: "server/schema.graphql"
Enter fullscreen mode Exit fullscreen mode

Documents

This is part of the frontend configuration. Documents should point to some schema definition of your operations (queries and mutations). Here's an example:

query AllPosts {
  allPosts {
    id
    author {
      displayName
      picture
    }
    title
    publishedAt
    content
    comments {
      id
      text
      username
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
documents: "client/**/*.graphql"
Enter fullscreen mode Exit fullscreen mode

The React Query Plugin

The installation process didn't gave us an option to add React Query. But we can easily integrate it thanks to the huge pugin hub!:

Fist, we need to install the right plugin:

npm install -D @graphql-codegen/typescript-react-query
Enter fullscreen mode Exit fullscreen mode

Then we configure it in codegen.yml configuration file by adding it to the plugins of the frontend section:

generates:
  client/src/generated.tsx:
    documents: "client/**/*.graphql" # where the queries are written
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-query"
Enter fullscreen mode Exit fullscreen mode

What's amazing about this plugin is that it's also going to take care of configuring the React Query client (endpoint, fetcher, etc) so that we can just use simple hooks, eg. useGetAllPostsQuery()

In order to make this work, we need to provide some configuration such as the GraphQL endpoint, but we can also add other things, e.g, an authorization header (with environment variables, how cool is that!):

plugins:
    - "typescript"
    - "typescript-operations"
    - "typescript-react-query"
config:
    fetcher:
        endpoint: "process.env.API_URI"
        fetchParams:
            headers:
                Content-Type: "application/json"
                Authorization: "Bearer process.env.HEADER_AUTH_TOKEN"
Enter fullscreen mode Exit fullscreen mode

Putting everything together

We are now ready to go!

To generate the types, we can simply run the command:

npm run codegen
Enter fullscreen mode Exit fullscreen mode

Use the generated types in the backend resolvers:

import type {QueryAuthorArgs} from "/generated"
import type {Context} from "./context"

const resolvers = {
    Query: {
        author: (
            _parent: null,
            { id }: QueryAuthorArgs,
            context: Context) => {
            // Do what you got to do to get the author...
        }
    }
    Mutation: {
        createPost: (
      _parent: null,
      { input }: MutationCreatePostArgs,
      ctx: Context
    ) => {
      // Save the post in the database!
    },
    }
}
Enter fullscreen mode Exit fullscreen mode

And use the generated hooks in the frontend like so:

import { useAllPostsQuery } from "./generated";

function App() {
  const { status, error, data } = useAllPostsQuery();
  ...
Enter fullscreen mode Exit fullscreen mode

Conclusion

If you decide to go down the code-first route (blue pill) good for you, but many teams decide to pick a schema-first approach to build their GraphQL API, and even though it's a great option, it can quickly become a burden to test and maintain your code.

But fortunately, graphql-codegen is an elegant solution to fix that problem of code duplication, making the schema file your single source of truth!

GraphQL Security

In one of our previous post, we shared how every GraphQL framework has zero security configured by default. Most GraphQL APIs are therefore subject to the most basic attacks (brute force, DoS, etc).

To compensate this lack of security in the GraphQL ecosystem, we built a quick scan that will get you started im your journey to shipping bullet proof applications!

You can run a dozen security scans on your GraphQL endpoint for free - no signup - at graphql.security

Discussion (1)

Collapse
redbar0n profile image
Magne

See: The Problems of "Schema-First" GraphQL Server Development

The red pill path is:

  • Back-end: Code-first (aka. resolver-first) using Nexus GraphQL, which outputs both GraphQL schema and TS types and keeps them in sync. Possibly with the Prisma (ORM) plugin to quickly expose DB fields without having to write resolvers.

  • Front-end: GQty (runtime gen). Alternatively GraphQL codegen (compiletime gen) to generate React useNamedQuery hooks with TS typings from the GraphQL schema (which is schema-introspected from the back-end). Possibly with graphql-codegen plugin called typescript-urql if using URQL GraphQL client (which is recommended).