GraphQL & TypeScript Magic
Sean Allin Newell
Posted on March 16, 2024
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 it we can eager load them all 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)
Posted on March 16, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024