A Reasonable GraphQL Exploration: Part 1

Type-Safe GraphQL Development using ReasonML and Apollo

Brandon Konkle
Ecliptic

--

Since my days of weaving complex data sets data together to deliver dynamic experiences at Craftsy, I’ve chased after ways to take better control of the chaotic world of the front end. I finally feel that journey is coming to fruition with the emergence of GraphQL, ReasonML, and sound, expressive typing.

Update: Part 2 can be found here, and Part 3 can he found here!

Why Reason?

There are a lot of great resources and excellent talks about the powers of GraphQL and how they can benefit front end developers, especially in the React ecosystem. Reason, however, is still relatively new and is just starting to see tentative adoption by people interested in type safety and functional programming. Key engineers at Facebook are investing heavily in it, however, along with partners like Bloomberg who open-sourced the BuckleScript JavaScript compiler that ships with Reason.

Its key features for me are that it provides sound and expressive types. Soundness means that your program can’t get into certain invalid states, and once it compiles the types are guaranteed to be accurate. Expressiveness means that you can effectively annotate the internal operations of your program in a way that both you and the compiler can understand. The types become an effective form of documentation. Type systems like the one found in Java are not very expressive. Popular type systems for JavaScript today like Flow and TypeScript are not sound.

Reason uses OCaml behind the scenes to provide a rich type system, and it leverages BuckleScript to enable rich foreign function interfaces and generate JavaScript output. It provides great features for hardcore Functional Programming — like pattern matching, auto-currying, and algebraic data types. OCaml also works well within the Object-Oriented Programming paradigm, making it a great match for JavaScript.

The Quest for Type Safety

My quest for stronger type safety began with PureScript and — by extension — Haskell. I wrote in a long-ago post how powerful PureScript’s compositional style of programming can be, embracing compatibility over standards. When working with PureScript I loved the power of unified typing, making changes where they made logical sense and allowing the type system to lead me to the impacts of each change. I also mentioned how the much more popular Elm language’s decision to exclude ad-hoc polymorphism and strictly limit foreign function interfaces disqualified it for me. In practical use, however, I spent a lot of time chasing down inscrutable type issues, difficulties with the side-effect system, and disruptive language and ecosystem changes.

I settled on Flow for a while, but at times it felt nearly as brittle as JavaScript itself. The types themselves were expressive enough to make great documentation even when the type checking broke down, but I never found the type-driven nirvana I searched for.

I’d tried Reason and BuckleScript nearly a year before, when it was earlier along and on a previous version of its syntax. I wasn’t hooked from the start, but it was enough to plant a seed. I returned to it recently while searching for a more solid Node server architecture. Version 3 of the syntax had just landed, and the new documentation site was outstanding. With these additional aids, OCaml finally clicked for me and I was immediately able to be productive!

Creating a GraphQL Server

As mentioned, I’d settled on GraphQL as my preferred technology for client/server interactions. I’ve used REST for many years, since my oft-cited Django development days, and I’ve always been frustrated by inconsistencies and errors. GraphQL presents a usable yet strict specification that handles many of the challenges of modern client/server applications on the web and beyond. The accompanying type language is a fantastic way to make concise contracts between client and server, and then automatically carry out interactions based on those contracts.

On the server side, this means that it eliminates the developer’s need to worry about endpoints and routing. A single JSON-driven endpoint is exposed, and the GraphQL specification determines how each query is handled. Rather than using Routes and Controllers, developers are encouraged to structure their application with Resolvers and Connectors. For my purposes, I like to stick with the Handlers and Services pattern that I adapted from James Gibson. No matter the source — GraphQL, HTTP, a WebSocket, or something else — requests are handed off to Handler functions to be fulfilled. Handlers work with one or more Services to actually retrieve or change data. When Services are initialized, they are given a DataProvider that gives them access to underlying data sources.

Start with a Schema

I love the idea of “GraphQL-first”, and I’ve been using GraphQL schemas in recent projects to create a common language to talk about data between the front and back end before the backend implementation even begins. I can then use the GraphQL schema to inform many other aspects of development. I’ve created an example project to demonstrate my structure here, and the GraphQL schema file is here. This is a small project managing PaperClip production, so the schema is pretty small. This file grows pretty large in practical projects, so I like to keep the Schema separate from my Queries and Mutations.

Queries and Mutations are how the user of your API interacts with your data. GraphQL handles these with top-level Query and Mutation types that you fill in to define your API’s operations.

The Schema: https://github.com/ecliptic/reason-graphql-docker/blob/master/graphql/schema.graphql

Queries and Mutations: https://github.com/ecliptic/reason-graphql-docker/blob/master/graphql/queries.graphql

As you can see, I’ve set up a simple CRUD interface to create PaperClips.

A Reasonable Representation

Next, I implement my type in Reason and set up some utilities for working with it. To get things started, I create a type. I create a PaperClip.re file, and start a type definition for a type called t. This is a convention in Ocaml, and it means that this is the core type for the PaperClip module.

We already have a minor challenge to deal with, the GraphQL enum. As you can see above, I’m referencing a Size.t type, but I don’t have a definition for it yet. This definition needs to go above the PaperClip.t type, and it needs to translate back and forth between the GraphQL enum type.

What is this module business? The Reason and OCaml docs describe these breathlessly as “mini files”, which made no sense in my mind at all. It fits together in my mind now that I understand each OCaml file is automatically a module, but a better comparison for me is the concept of Classes. Defining a Size module here is a little like defining a class Size in an ES6 JavaScript module. I don’t need one here, but the OCaml community uses the name make by convention to refer to “constructor methods”, which are actually just functions in the module that return a value matching the core t type.

My t type sets up a Variant, OCaml’s implementation of pattern matching. ReasonML commandeers JavaScript’s switch statements and amplifies them to take the type system into account. Classic switch statements can only make decisions based on values, but ReasonML switch statements can also make decisions based on types. My Size.fromString and Size.toString functions take advantage of this to translate back and forth from GraphQL’s string representation.

Resolving your PaperClip

Next is Resolve, a module I define for use in two places — both in the GraphQL resolvers and in general JSON encoding. Both uses are slightly different, mainly because GraphQL allows for custom types with their own serialization logic and they need to be handled differently than they would for JSON.

My module simply defines how each attribute on a PaperClip instance is accessed, including minor logic like the Size.toString function. The |> operator is some particularly tasty sugar, denoting a pipeline similar to Ramda’s compose or Lodash’s flow. It’s called the “reverse-application operator”. In this case, it merely means that I’m calling Size.toString with paperClip.size as the argument. I could add additional functions to the pipeline simply with more |> characters, which is the magic of this syntax.

Encoding and Decoding JSON

Next up are my Encode and Decode modules, which I use with https://github.com/reasonml-community/bs-json to translate back and forth between JSON.

I’m putting that reverse-application operator to good use here, passing my PaperClip.t object and my json objects through tools provided by bs-json to help navigate the chaotic endeavor of statically typing JSON. I’m using the open Resolve; statement towards the top of the module to make the members of the Resolve module available to me without using the Resolve. prefix. As you can see, a little extra massaging is needed to the dates to make them suitable for JSON.

Final Exports

Finally, I export a couple of useful types and the resolvers interface that we’ll be using with Apollo, making use of my Resolve module. The dates here are exported as-is, because I have a “custom scalar type” to handle them in GraphQL.

What’s Next?

I’m going to have to bring this first post to a close here, but if you want to read ahead before the next post you can see the full implementation here. I’m opening the project up to feedback from the Reason & BuckleScript communities so that I can learn and refine my approach, and I’ll keep the repository up to date as a reference implementation.

My next post will cover the PaperClipService and PaperClipHandler. The Service uses Knex to interact with PostgreSQL to store and retrieve PaperClip instances, and exposes a semantic interface for working with them. The Handler provides a GraphQL-friendly interface and translates requests to Service method calls.

Part 3 will step back and plug our PaperClip API into a Reason-driven Express server that can also serve other endpoints, giving you great flexibility with strong type safety.

In the meantime, if you’re looking for agile React, React Native, and Node development from an innovative team with a passion for software craftsmanship — contact me! Ecliptic is growing, and we’re always happy to talk about new projects!

--

--

Brandon Konkle
Ecliptic

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