A Reasonable GraphQL Followup

Updates to my GraphQL architecture as my application grows

Brandon Konkle
Ecliptic

--

I’ve been pushing forward with my GraphQL stack, preparing for production in the near future. I haven’t had time to polish it like I’ve wanted, but I’ve definitely learned a lot and have made significant changes to the structure. I’ll be updating my example app soon, but I wanted to call out some of the big differences from my previous posts to share how my solution has progressed so far. I’d love to hear from you if you’re doing something similar!

Encoding and Decoding Impacts Performance

Khoa Nguyen — a fellow GraphQL explorer I met through the busy Reason Discord Chat — did some benchmarking and found that encoding JSON to OCaml data structures via bs-json was taking more than 100ms per object in basic testing. For many uses, this isn’t a problem. For a high-traffic server endpoint, however, this can stack up quickly and make a huge difference.

In my previous articles, I kept my data types next to their service implementations. Along with my refactor taking the above information into account, I consolidated my data schema into a single Schema.re file with only plain JS objects represented. I’m matching them to my Postgres schema for easy insert and update statements.

I also keep my input types close to the Postgres snake_case format, even though my actual GraphQL Schema is in camelCase format. This is for my convenience as a backend developer — though it makes things a bit confusing when I put on the front-end hat. I’ll solve this dichotomy later, though. 😅

Using the GraphQL Context

One of the errors in my original posts (which I’ve since corrected), incorrectly assumed that the first argument to a query or mutation resolver was the context. The first argument is actually the rootValue , and the context is passed in as the third argument.

You can make use of the context in the Router.re module. Rather than using createGraphQLExpressMiddleware, you’ll want to use the more powerful graphqlExpressAsync function:

This function provides the Express request object, allowing you to pull details from it provided by your authentication middleware or any other tool.

You can use this context in your handlers to make authorization decisions (though you should note the example below is very weak):

JSON Error Handling

To help the front end parse error messages, I like to make sure they’re always provided in the same format as other responses from my endpoints — JSON. I attach middleware to my Express app at the end of the chain, right before the listen call:

This is working well so far, but I’ll extend it soon to handle different response codes and save usable-but-secure data to production errors.

Don’t Re-use the Knex Object

One very confusing bug was solved when I realized that my fromTable utility retained some context from one request to the next, polluting queries from one request with data from another request. To solve this, I thunked the fromTable call, so that a fresh Knex query is returned each time the service is called.

The Frontend is On the Way!

I’ve been diving into reason-react and reason-apollo to implement my frontend, and I’m loving the experience so far! The level of control that the type system gives you is outstanding, though it allows you to cheat when you feel the need. I’m cheating as sparingly as I can — I want to limit the chaos as much as possible.

I’ll be writing a frontend-focused article soon that dives into the patterns and practices I’m building in ReasonML in the browser. I hope to dig into ReasonML on React Native soon after, and I’ll have a great type-safe universal application stack on my hands! 😎

In the meantime, we’re looking for new contract work at Ecliptic, and we’d love to hear from you! If you need help with a React, Node, or an FP-centric tech like ReasonML, we’d be glad to talk with you!

--

--

Brandon Konkle
Ecliptic

Founder and Lead Developer at @eclipticdev, @reasonml acolyte, supporter of social justice, enthusiastic nerd, loving husband & father.