GraphQL: Tips after a year in production

Matt Krick
Dec 18, 2016 · 7 min read

When I wrote Meatier a little over a year ago, I was pretty early in adopting GraphQL to replace my REST endpoints, my ORM, and my imperative client-side data requests. Shortly after, when I was hired to build an open-source realtime app from the ground up, I jumped at the chance to use GraphQL both on the server and as the basis for my own client cache. A year later, I learned that building a GraphQL app for production is a lot different than one of those demo apps you see on GitHub. Go figure. After all the mistakes I made, here are my lessons learned.

GraphQL is flexible. When starting out, you often wonder if you’re doing it the “right” way. Should GraphQL talk to an ORM like mongoose, or does it go straight to the database? Do you create a library of custom scalars for passwords, paragraphs, and titles, or do you use external validation? Do you favor many, smaller mutations (eg updateName, updateTitle) or larger, more powerful ones (eg updateUserDoc)? Should you use it for Auth? (Yes) How about as an intermediary for an external API? (Yes!) Can it do subscriptions yet? (Kinda). Let’s dig in.

1. Folder Structure

Yawn fest. But seriously, keeping your GraphQL schema modular is critical. I keep a discrete folder for each DB entity (Post, Comment, etc.) and inside is a discrete file for every operation (PostQuery, PostMutation, PostSchema). Explaining folder structure in words sucks. Check it out here. Now on to the interesting stuff…

2. Connecting to a DB with Node.js

In production, you use a driver to connect to your database. All demos import their DB driver at the top of file. This is crap for one major reason: it isn’t lazy! When you start your server, your GraphQL endpoint is going to resolve your GraphQL schema file, which in turn will resolve your db driver and start that connection. By eagerly instantiating the DB connection, you increase the time it takes to start up your server. More importantly, your connection is now stateful so any webpack magic that uses your schema (think creating a webpack bundle for server-side rendering) will need to manage that connection and end it when necessary. Imagine building a universal app that concurrently builds the client and server bundles & then maybe starts up the server. It’s like asking the last person out to close the door, but you can’t see or hear anyone. Spaghetti code becomes inevitable. To avoid the headache, I only establish the connection when necessary:

// getDBDriver.js
let driver;
export default function getDBDriver() {
if (!driver) driver = startDriver();
return driver;
};

Now, instead of importing the driver, I import my wrapper & just call that in the resolve method:

// in userSchema.js
resolve(source, {id}) {
const db = getDBDriver();
return db.get(id);
}

Now my imports don’t have side effects. Yay closures!

3. Structuring Resolve Functions

In every GraphQL demo ever, the resolve function does nothing but resolves. Must be nice. In production, you have attackers calling your GraphQL endpoints trying to query documents that don’t belong to them. Even if someone owns the document, they may try to pass in values that shouldn’t be allowed (eg userCredits = $1000000). So how can you protect yourself while keeping your code readable? My answer is something I call the Auth/Validation/Resolution pattern. To describe it, let’s assume we have an app with a form allowing a person to change their team name:

async resolve(source, {updatedTeam}, {authToken}) {
const db = getDBDriver();

// AUTH
requireUserOnTeam(authToken, updatedTeam.id);

// VALIDATION
const validate = makeTeamSchema();
const {errors, data: {id, name}} = validate(updatedTeam);
handleSchemaErrors(errors);

// RESOLUTION
return await db.table('Team').get(id).update({name});
}

In 5 lines of code, I’ve established authentication and authorization. I’ve validated and normalized the arguments, and I’ve given back the result. Let’s break it down.

Auth

In an entire enterprise SaaS, I found that each mutation only requires 1 of about 5 unique auth checks. (Are they signed in? Are they editing something that belongs to them or their team? Do they have an active websocket connection?). These are inexpensive checks to make sure the user can do what they want to do. Ideally, they’re synchronous. To make them synchronous, I sometimes cheat & sneak in extra info in the primary key. For example, if a user has a to-do item, and that to-do item can’t change users, I’ll make the id user123::xYz8yUo. When a user tries to update it, we can compare the to-do’s id with the id from their authToken/cookie. This type of thing becomes critical when you have a realtime app and are editing the same document in sub-second intervals. I don’t care if you can hack an election, you still can’t change the primary key in a DB.

Validation

Now that we know the user is good, we need to see if the data is good. Sure, you probably already validated this on the client, but rule #1 of a secure web app is to never trust the client. To do this, I use a function that both validates and normalizes (eg String.trim()) the data. There are a bunch of popular libraries out there like the bloated Joi and the async Yup, but I roll my own 100 LOC solution that plugs directly into redux-form so both client and server can use the same validation schema.

Why no GraphQL custom scalars? I thought that was the way to go, but I was wrong. Unfortunately, they only flirt with being useful. They are long on verbosity and short on power. For example, if I want to validate a new user’s email address, I’ll need a regex, but I’ll also need to make sure it doesn’t already exist in my DB. If I need to validate that in the resolve function anyways, I might as well do the regex in there, too (and return a client error message far prettier than the ugly mess GraphQL gives me).

4. Writing your Schemas

Some folks like using the GraphQL shorthand to write a big text blob schema. I don’t do that for the same reason I don’t write all my JavaScript in a text file & call eval() on it. It’s harder to write and I like the colorful error squiggles my IDE gives me. Sure, in the future an editor might detect GraphQL bits of code & lint them on the fly, but that day isn’t today.

As for descriptions, there are 2 important things to include:

  • An asterisk to denote a field that’s indexed in the DB (so those without direct DB access can still write efficient queries)

5. Writing your Mutations

The age old question: would you rather fight 1 horse-sized duck, or 100 duck-sized horses?

Image for post
Image for post
Honestly, I’d take the horses. I’m from the country. I’ve had to run from angry ducks.

It applies to GraphQL, too. Should you write 1 mutation to handle data from 100 different forms, or write 100 different mutations for every different way a document might be edited? While the correct answer is always “it depends”, I like to default to larger, more powerful mutations because it lets you iterate faster. If your form gets a new field, you add it to the front-end, you add it to your GraphQL schema, your validation schema, and you’re good to go. You’ll know it’s time to break it apart when you start seeing multiple massive conditional blocks in your resolve function and a growing number of arguments.

Final Remarks

That’s it! Your intermediary course in GraphQL is complete. If you want a deep dive into auth and error handling in GraphQL, you can always check out my GraphQL Field Guide to Auth. Am I full of crap? Let me know in the comments.

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

Image for post
Image for post

HackerNoon.com

#BlackLivesMatter

Sign up for Get Better Tech Emails via HackerNoon.com

By HackerNoon.com

how hackers start their afternoons. the real shit is on hackernoon.com. Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Matt Krick

Written by

Building the future of work

HackerNoon.com

Elijah McClain, George Floyd, Eric Garner, Breonna Taylor, Ahmaud Arbery, Michael Brown, Oscar Grant, Atatiana Jefferson, Tamir Rice, Bettie Jones, Botham Jean

Matt Krick

Written by

Building the future of work

HackerNoon.com

Elijah McClain, George Floyd, Eric Garner, Breonna Taylor, Ahmaud Arbery, Michael Brown, Oscar Grant, Atatiana Jefferson, Tamir Rice, Bettie Jones, Botham Jean

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store