GraphQL & TypeScript Magic

GraphQL & TypeScript Magic

If you haven't heard, there's a new player in town when working with a GraphQL Schema in TypeScript.

šŸŖ„ gql.tada (repo)

šŸŒŸ
"...with gql.tada and GraphQLSP you get on-the-fly, automatically typed GraphQL documents with full editor feedback, auto-completion, and type hints!"

- gql.tada readme

What magic is this? Let's see!

We can use an existing GraphQL API and Schema, so we can focus on just a GQL Client workflow. There's a wonderful list of many GQL apis, so pick your favorite, we're using Trevor Blade's Countries API.

You can find the final code here.

Setup

Create a new astro site, then add some relevant libraries following the gql tada instructions and urql instructions, and our editor and code boilerplate will be ready to go.

mkdir gql-tada-explore
cd gql-tada-explore
pnpm create astro@latest . # choose strictest TS
pnpm astro add react
pnpm add lodash.debounce gql.tada urql @urql/exchange-graphcache
pnpm add -D @0no-co/graphqlsp @types/lodash.debounce
setup instructions

Update your tsconfig with the following plugin configuration:

 {
   "plugins": [
      {
        "name": "@0no-co/graphqlsp",
        "schema": "https://countries.trevorblades.com/graphql",
        "tadaOutputLocation": "./src/graphql-env.d.ts"
      }
    ]
}
tsconfig.json

Create a client.ts file to setup urql (cache is optional, but I'd recommend it)

import { Client, fetchExchange } from 'urql';
import { cacheExchange } from '@urql/exchange-graphcache';

export const client = new Client({
  url: 'https://countries.trevorblades.com',
  exchanges: [cacheExchange({}), fetchExchange],
});
src/client.ts

In src/component folder, create a react component App.tsx. We will be using <App /> as a pure SPA so we can get going quickly. We will pull in the client we declared above to provide our gql client to all useQuery calls.

import { Provider } from 'urql';
import { client } from './client';

export const App = () => {
  return <Provider value={client}>
    <h1>Hello from React</h1>
  </Provider>
}
src/App.tsx

Use our App as a client SPA in src/pages/index.astro as a client only react component.

---
import { App } from "../components/App";
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
      <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
      <meta name="viewport" content="width=device-width" />
      <meta name="generator" content={Astro.generator} />
      <title>Country Search</title>
  </head>
  <body>
    <App client:only="react" />
  </body>
</html>
src/pages/index.astro

Now, to see the magic of our setup, we're going to create a placeholder SearchBar component in src/components and use it in our App.tsx - it won't do anything yet, but that's where we'll start issuing GQL Queries and use the data.

Magic

Before proceeding - try to do the following task on your own

āœļø
Load all continents from one query, so we have each Continent's code and name available. There are not that many Continents on the planet, so we can eager load them all at once and do our searches in memory. Try your hand at implementing a search by name in memory and display matching continents in a list.

Now that we are all set up, we can see the šŸŖ„ magic in action. In our SearchBar, import gql from gql.tada, and start authoring a new query in the tagged template literal and you will now get end to end autocomplete and types. Beautiful.

type safe completions at query author time

Now that we are authoring queries it's time to see how this works, and how far the intelligence goes.

Lints

If we query for continents, including their code and name, but only access the name - we get a TypeScript warning telling us we are, essentially, over fetching!

All The Types

When a project gets going, you often want to create components to display child nodes of a large query, gql.tada gives us some type helpers so we can cut through the GQL and get to the useful TypeScript bits quickly, look at this code example, taken from src/queries.ts in the codebase

import { graphql, type ResultOf } from "gql.tada";

export const getTopLevel = graphql(`
query getTopLevel {
  continents {
    code
    name
  }

  countries {
    code
    name
    continent { code }
    emoji
    capital
    currencies
  }
}`);

type TopLevel = ResultOf<typeof getTopLevel>;

export type Continents = TopLevel["continents"];
export type Countries = TopLevel["countries"];

export type Continent = Continents[0];
export type Country = Countries[0];
extract types

Make an App

Now that we've explored some of the powers and how to make use of gql.tada - the full app in the repo has all the code, but take the time to play with it yourself and fulfill the following requirements:

  • Search all continents by Code or Name.
  • Implement tab and enter and click to select a Continent.
  • Show a Continent Card that displays all Countries on the selected Continent.
  • When you select a Country, show a Country Card that displays information on the selected Country.
  • Construct wikipedia links to relevant articles on Countries and Capitals
  • Construct financial links to relevant currency comparisons.

Some further ideas and explorations you may want to consider:

  • Find a GQL API you can locally host, to test how gql.tada + urqlg handles mutations as I have largely skipped over that here.
  • Find a large GQL API and see if the latest optimizations will still retain the snappiness (consider pulling the schema into a local file)