17 Apr 2023
Let's begin with a basic GraphQL Yoga setup. I'll be using Cloudflare Workers but you can serve GraphQL however you want.
npx wrangler init hello-garph
Next we'll install GraphQL and GraphQL Yoga:
npm i graphql graphql-yoga
If we now begin to construct the GraphQL Yoga server using createYoga
and createSchema
, we'll notice that when we define the resolvers we get absolutely no type safety.
import { createYoga, createSchema } from "graphql-yoga";
const schema = createSchema({
typeDefs: /* GraphQL */ `
type Query {
cart(id: ID!): Cart!
}
type Cart {
id: ID!
totalItems: Int!
items: [CartItem]
subTotal: Money!
}
type CartItem {
id: ID!
name: String!
}
type Money {
amount: Int!
formatted: String!
}
`,
resolvers: {
Query: {
cart: (_, { id }) => ({
id,
totalItems: 1,
items: [{ id: "item-1", name: "Stickers" }],
subTotal: {
amount: 1000,
formatted: "£10",
},
}),
},
},
});
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const yoga = createYoga({
graphqlEndpoint: "/",
landingPage: false,
schema,
});
return yoga.fetch(request, env, ctx);
},
};
We explored in episode 26 how we can use the GraphQL Code Generator to automatically create types for resolvers.
But what if I told you there was another way...
garph is a great tool that can be used to build the schema, and resolver types by using just code. If you've seen Pothos or Nexus, this will be familiar.
Let's first install garph
using NPM:
npm install garph
Now let's update the src/index.ts
and remove the schema
const and createSchem
import. We'll use garph instead.
import { createYoga } from "graphql-yoga";
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const yoga = createYoga({
graphqlEndpoint: "/",
landingPage: false,
schema,
});
return yoga.fetch(request, env, ctx);
},
};
Now add the garph
imports to our src/index.ts
:
import { g, InferResolvers, buildSchema } from "garph";
We had earlier the SDL for our Query
type:
type Query {
cart(id: ID!): Cart!
}
We can use the import g
from garph
to reference cart
, pass some args
and a description
:
import { g, InferResolvers, buildSchema } from "garph";
const query = g.type("Query", {
cart: g
.ref(cart)
.args({ id: g.id() })
.description("The ID of the Cart you want to retrieve".),
});
It's that easy! This will generate the same GraphQL schema we wrote earlier:
type Query {
cart(id: ID!): Cart!
}
You'll notice we're ref
erencing cart
in the query
type but we haven't yet defined this. Let's do that now:
const cart = g.type("Cart", {
id: g.id(),
totalItems: g.int(),
items: g.ref(cartItem).list(),
subTotal: g.ref(money),
});
Now let's finish by creating the cartItem
and money
types:
const cartItem = g.type("CartItem", {
id: g.id(),
name: g.string(),
description: g.string().optional(),
});
const money = g.type("Money", {
amount: g.int(),
formatted: g.string(),
});
If we wanted to make CartItem
a union type, we can do that with garph
too:
const promotionItem = g.type("PromotionCartItem", {
id: g.id(),
name: g.string(),
description: g.string().optional(),
});
const skuItem = g.type("SkuCartItem", {
id: g.id(),
name: g.string(),
description: g.string().optional(),
});
const cartItem = g.unionType("CartItem", {
skuItem,
promotionItem,
});
Now all that's left to do is create a new resolvers map and use the special import InferResolvers
, passing in a generic for the Query
type:
const resolvers: InferResolvers<
{ Query: typeof queryType },
{ context: YogaInitialContext & { env: Env } }
> = {
Query: {
// ...
},
};
Now when we begin to type inside the resolver map for Query
we have full type safety!
const resolvers: InferResolvers<
{ Query: typeof queryType },
{ context: YogaInitialContext & { env: Env } }
> = {
Query: {
cart: (_, { id }) => ({
id,
totalItems: 1,
items: [{ id: "item-1", name: "Stickers" }],
subTotal: {
amount: 1000,
formatted: "£10",
},
}),
},
};
You'll notice we haven't had to configure any tools to generate code but simply import the utilites from garph
to generate our type safe schema!
To finish and connect this to GraphQL Yoga all you need to do is invoke buildSchema
:
const schema = buildSchema({ g, resolvers });
interface Env {}
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
const yoga = createYoga({
graphqlEndpoint: "/",
landingPage: false,
schema,
});
return yoga.fetch(request, env, ctx);
},
};