graphql
typescript
last updated: October 13, 2021

GraphQL + TypeScript, a love story

How I use GraphQL, TypeScript, a VSCode extension and GraphQL Code Generator to create a robust api with little room for mistakes

Introduction

I love working with GraphQL. Especially because it is typed. This makes it the perfect match for TypeScript. With GraphQL exactly returning what you query, add a VS Code extension for checking the schema and a code generator to return typed results and we'll build a type safe api that will prevent us from messing up.


This project has it's own Github repo.

Setup

When we have a base project we npm i -D typescript ts-node to use TypeScript in Node. Let's generate a tsconfig.json with npx tsc --init. For this blog we stick to the default config.

Inside package.json add the following 'start' script.

{
  "scripts": {
    "start": "ts-node index"
  }
}

Create index.ts. Inside test if it works with console.log('test'). If we enter npm run start in our console we'll see 'test' logged. For code hygiene I'm using eslint and prettier. If you want to use this too I'd recommend you checkout the Github repo.

Now we're ready to use GraphQL.

GraphQL Request

We're going to fetch our data from a fake data api. You can play around with the api here.

To get this date we're going to create a client to request information with. My favorite is graphql-request. Install with npm i graphql-request.

Now we can create a client with the fake data url.

index.ts
import { GraphQLClient } from 'graphql-request'

const client = new GraphQLClient('https://graphqlzero.almansi.me/api')

We can use this client to create a request. Let's write a simple query first and then bring it together.

index.ts
import gql from 'graphql-tag'

const query = gql`
  query GetUser {
    user(id: 1) {
      id
      name
    }
  }
`

Bringing it together.

index.ts
import { GraphQLClient } from 'graphql-request'
import gql from 'graphql-tag'

const client = new GraphQLClient('https://graphqlzero.almansi.me/api')

const query = gql`
  query GetUser {
    user(id: 1) {
      id
      name
    }
  }
`

const getQuery = async () => {
  const res = await client.request(query)

  console.log(res)
}

getQuery()

We got a query client, a query and a function to request the information. When we run npm run start again we'll get a return like { user: { id: '1', name: 'Leanne Graham' } }.

If we add username to the query we'll receive the same result plus the username. Let's do that.

index.ts
const query = gql`
  query GetUser {
    user(id: 1) {
      id
      name
      username
    }
  }
`

Nice to meet you 'Bret'. This is pretty simple but how do we know we can just add username to the query? We can just check the docs in the playground. But there's a better way.

VSCode extension

What if I tell you that, with a few lines of code and an extension, you can check what your query options are without even leaving your editor.

With this extension VSCode will not only highlight your code, it will also, with the right instructions, check your query with the schema. All we need to do is install it and add a .graphqlrc.yml file.

.graphqlrc.yml
schema: "https://graphqlzero.almansi.me/api"
documents: "*.{graphql,js,ts,jsx,tsx}"

That's it. Now when you add a field to the GetUser query, your editor will give you hints.

GraphQL Code Generator

Why

But there is still an area where we can mess up. What if we don't console log a user but console log a car. console.log(res.car) will have no success. Don't worry, GraphQL Code Generator to the rescue. Codegen will return a typed result from your queries.

Installation

Let's install GraphQL Code Generator. This codegen generates code from your GraphQL schema and operations. npm i -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations.

All we have to do is to create a codegen file.

overwrite: true
schema: 'https://graphqlzero.almansi.me/api'
generates:
  gen-types.ts:
    documents: './index.ts'
    plugins:
      - 'typescript'
      - 'typescript-operations'
hooks:
  afterAllFileWrite:
    - 'prettier --write'

In our package.json we add "codegen": "graphql-codegen --config codegen.yml" to our scripts. Run npm run codegen. We generated all the types for the schema.

Using the types

In our index we can add these types and change our query.

index.ts
import { GetUserQuery } from './gen-types'

const getQuery = async () => {
  const res = await client.request<GetUserQuery>(query)

  console.log(res)
}

Now you can get all the types on 'res'. Try to type console.log(res.). You'll see that res has a user key. If we enter console.log(res.user.) we'll see that the user has all the fields included in our query. But not email for example.

Generated queries

This feels kind of tedious. Why do we have to import the gen-types on every request just to get the types. If you'd make 10 queries this would be a lot of repetition. And we don't like that.

That's why we're going to generate the query. With just one more plugin added to the codegen. npm i -D @graphql-codegen/typescript-graphql-request. Add this package to the plugins in your codegen file.

codegen.yml
overwrite: true
schema: 'https://graphqlzero.almansi.me/api'
generates:
  gen-types.ts:
    documents: './index.ts'
    plugins:
      - 'typescript'
      - 'typescript-operations'
      - 'typescript-graphql-request'
hooks:
  afterAllFileWrite:
    - 'prettier --write'

Now when we generate we can make a few tweaks to the client.

import { GraphQLClient } from 'graphql-request'
import { getSdk } from './gen-types'
import gql from 'graphql-tag'

const gqlClient = new GraphQLClient('https://graphqlzero.almansi.me/api')

const client = getSdk(gqlClient)

const query = gql`
  query GetUser {
    user(id: 1) {
      id
      name
      username
    }
  }
`

const getQuery = async () => {
  const res = await client.GetUser()

  console.log(res.user)
}

getQuery()

We change the import from the gen-types. Then we changed the client. Finally we fetched the user a little different. Every query you'll write will call-able from the client object.

Conclusion

GraphQL and TypeScript hand in hand prevent us from making mistakes. When we know that GraphQL is going to return what we request, we know what we can and cannot request from the schema and we know what we can get from the result object, there's little room for making mistakes. I hope you enjoyed this blog and I saved you a couple of headaches. Let me know what you think of this practice and join me in my newsletter. Thank you.