Type-Safe GraphQL app-schema resolvers!

Frank Sandqvist
Smidyo Codex
Published in
3 min readMay 9, 2018

--

Graphcool’s Prisma (and GraphQL overall) is amazing. I can’t believe how fast I’m developing our back-end, moving back-and-forth between changing the database layer’s schema and the app schema.

However I ran into a bit of a snag when I was going to start using my newly “graphql prepare”-generated app-schema typings. (using the binding-ts generator)

Taking a look at my src/generated/app.ts file I see this:

The parent argument is missing! Which is also understandable, the GraphQL bindings do not really have any use for that here.

So we can forget doing something like this:

Linting equivalent of a Christmas tree.

Clearly, this won’t work, since graphql-yoga provides the parent argument first. We don’t want the request’s argument types on the parent resolver argument, we want it on the args. We need to shift ‘em.

We could simply call another function inside of the resolver that has the types applied to them manually, like this:

Typo! Should be “return mutation(args, ctx, info)”

Works, now we have our request’s argument types. But it’s ugly, boiler-platy and we’ve got two sets of variables (at least in this implementation).

Enter Typescript voodoo!

All we’re doing here is taking the arguments that graphql-yoga provides and getting rid of the parent argument. The clever stuff is in the Typescript however: by inferring the argument typings of the generated Resolver type, we can get syntactically nice looking code:

Awesome. You just specify what interface you wish to get the function type from, and then the key of the resolver you’re programming! And since this is all typed, any change to the database schema will give you red squiggles immediately.

What if you need to access the parent object? One could do something like this:

It’s not the most elegant solution. I ran into a wall trying to write a utility function for this, block scoping and advanced TypeScript wrestled me to the ground. But it’s not too shabby I think!

Getting the app’s Context interface applied to all resolver functions automatically would be a great feature.

Or perhaps sometime in the future we will have interfaces as argument types, instead of hardcoded into the type definition. Would definitely make this post useless. :)

Feel free to give a few claps if you found this useful. Sound off with perhaps a better solution in the comments?

--

--