Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
At Meeshkan, we were writing our own typescript interfaces for JS object representations of our GraphQL schema, which proved to be error-prone and laborious.
The new graphqlgen package by Prisma makes that crazy easy, but if you are not comfortable with GraphQL schemas yet, it can feel like another layer of incomprehension in an already incomprehensible stack. I wanted to take a moment to share a few key learnings from working with this new tool in case you are struggling to get started:
- How resolvers in Apollo Server actually work.
- Why graphqlgen is awesome.
- Why you should never use prisma types for your server’s schema’s types.
- Bonus: How to unit your test resolvers.
How resolvers work in Apollo Server
Before you can work with graphqlgen effectively, you need to understand the basics of how resolvers work in Apollo Server.
The main thing that helped me in grocking resolvers was to learn how they cascade, not unlike redux reducers. The documentation from Apollo Server is a helpful start, but I couldn’t quite figure out how everything tied together I concocted a bad example showing the problem they’re trying to solve.
Let’s assume that your schema is:
type User { id: ID! @unique posts: [Post!]!}
type Post { id: ID! @unique author: User!}
type Query { user(id): User}
One (bad) way you could write your resolvers is like this:
export const Query: MyQueryResolverType = { user: async (_, { id }, __) => { const user = await getUserFromDatabase(id); // fetch user const posts = await getUserPostsFromDatabase(id); // fetch posts return { ...user, posts, } }}
export const User: MyUserResolverType { id: ({ id }, _, __) => id, posts: ({ posts }, _, __) => posts,}
export const Post: MyPostResolverType { id: ( { id }, _, __) => id, author: ({ author }, _, __) => author,}
There are numerous problems with the code above: we have had to write our own types for resolvers like MyQueryResolverType, MyUserResolverType and MyPostResolverType. graphqlgen will take care of these for us, which we’ll see in a bit, but for now we’re stuck with that fragility.
The main problem for this section is that the user query is doing both too little and too much work. We have no clue if the incoming graphql query is going to even ask for the posts, so why would we fetch them here? Furthermore, if the person does something like:
{ user(id) { posts { author { posts { author { id } } } } }}
…this will break, as it won’t recurse down the tree. So let’s fix it with proper resolvers.
export const Query: MyQueryResolverType = { user: (_, { id }, __) => getUserFromDatabase(id); // a promise}
export const User: MyUserResolverType { id: ({ id }, _, __) => id, posts: ({ id }, _, __) => getPostsFromUser(id), // a promise}
export const Post: MyPostResolverType { id: ({ id }, _, __) => id, author: ({ id }, _, __) => getUserFromPost(id); // a promise}
And no more breakage. ApolloServer traverses down the tree, calling the correct resolver as needed. This is because the first argument passed in to the resolver function is the representation of the object returned from the previous resolver, which we use to get important information about the object like the id. In the ApolloServer documentation, this object is called parent, which I think is a poor choice for a name. I would opt for partial_obj or obj_hint or obj_with_stuff_missing.
Why graphqlgen is awesome
The “good” example above has (at least) two pesky anti-patterns: the Resolver type needs to be written by hand, and there are functions like Post.id that are basically identity functions. graphqlgen fixes this. It will automatically generate Typescript interfaces for resolver types and default resolvers for resolvers. Goodbye boilerplate!
To use graphqlgen, simply include a graphqlgen.yml file in your root directory, yarn add --dev graphqlgen and then npx graphqlgen somewhere in your build to generate the goods. The graphqlgen.yml file has, at the time of this writing, six keys that we use, all of which are pretty self-explanatory:
# The target programming language for the generated codelanguage: typescript
# The file path pointing to your GraphQL schemaschema: ./src/schema.graphql
# Type definition for the resolver context object# The context object is passed into ApolloServer.# It usually contains stuff like a database connection# or user id or whatever.context: ./src/types.ts:IContext
# Find TS models that correspond to the graphql schema and map them# NOTE: this part is tricky and is the subject of the next section,# so read on to find out more.models: files: - path: path/to/my/resolver/types.ts defaultName: 'I${typeName}' # my convention - path: path/to/my/generated/prisma/types defaultName: '${typeName}Node' # prisma convention
# Where all of this boilerplate code is writtenoutput: ./src/generated/graphqlgen.ts
# In case you need inspiration for your resolvers, this generates# stubs that you can fill inresolver-scaffolding: output: ./src/generated/tmp-resolvers/ layout: file-per-type # one file per type in the
Now, if we revisit our previous example, things get a whole lot easier to follow:
export const Query: QueryResolvers.Type = { user: (_, { id }, __) => getUserFromDatabase(id); // a promise}
export const User: UserResolvers.Type { ...UserResolvers.defaultResolvers, posts: ({ id }, _, __) => getPostsFromUser(id), // a promise}
export const Post: PostResolvers.Type { ...PostResolvers.defaultResolvers, author: ({ id }, _, __) => getUserFromPost(id); // a promise}
All of the resolver types are hanging out in src/generated/graphqlgen, and we no longer need to roll our own. Furthermore, default resolvers are generated for every type, so we no longer need to write all that boilerplate. Sweet!
Why you should never use prisma types for your server’s schema’s types
Prisma is fantastically efficient. So efficient that you will just want to ping your database from your client using GraphQL and dispense of Apollo Server entirely. This is tantamount to reading and writing to a database directly from a web page which is, in short, a very bad idea.
Now that graphqlgen is around, it is even more expedient to create an Apollo Server in record pace, and in doing so, you may be tempted to use the interfaces generated by Prisma as the interfaces representing types in your schema. This is also a bad idea. For example, let’s imagine that your prisma datamodel.prisma contains:
type UserNode { id: ID! @unique email: String! internalFieldUsedByDevs: String! anotherInternalField: String!}
whereas our schema.graphql contains:
type IUserNode { id: ID! @unique email: String}
If you do not want internalFieldUsedByDevs and anotherInternalField to be reachable by your client, you probably do not want the same Typescript type as prisma generates. The more sensible alternative would be to write your own type and use that. No problem, except how does graphqlgen know how to use yours and not prisma’s? Let’s go back to graphqlgen.yml.
models: files: - path: path/to/my/resolver/types.ts defaultName: 'I${typeName}' # my convention - path: path/to/my/generated/prisma/types defaultName: '${typeName}Node' # prisma convention
Here, order matters. graphqlgen will first use the types in types.ts and then try to get whatever it does not find from the generated prisma types. That way, Query.me will correctly be documented and type-checked as the IUserNode that we wrote and not UserNode generated by prisma.
While this seems trivial at first, as your server schema and prisma schema start to diverge, you will wind up in situations where your server schema is not a strict subset of your prisma schema. Thus, separating the two will become essential, and it’s better to get in the habit of enforcing that early.
Bonus: How to unit test your resolvers
Doesn’t really have anything to do with graphqlgen, but hey, since we’re talking about writing resolvers, let’s not forget to test them!
import * as request from "supertest";import app from "../../path/to/my/express/app";
const ENDPOINT = "/graphql";
test("test hello", async () => { const { text } = await request(app) .post(ENDPOINT) .send({query: "{ hello }"}); expect(JSON.parse(text).data.hello).toBe("Hello!");});
Underwhelming, I know, but the cleanest things often are. As I couldn’t really find anything online that stated this this succinctly, I thought I’d put it here.
This assumes that you have set up an express app in a separate file from your server so that you don’t have to start and stop the server for each test, which can lead to all sorts of wacky port-related errors.
We’re using Jest at Meeshkan, but mocha or really anything is fine. At some point, you’ll need to mock your database communication object (in our case, the prisma instance generated by prisma generate), which is pretty straightforward on any of these platforms.
So there you have it! Almost-painless type checking, default resolver scaffolding and unit testing. w00t!
Using graphqlgen for our cloud stack was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.