GraphQL Stack in Node.js: Tools, Libraries, and Frameworks Explained and Compared

GraphQL Stack in Node.js: Tools, Libraries, and Frameworks Explained and Compared

Introduction

GraphQL is often seen as the alternative to RESTful API. While there are clear advantages to creating GraphQL APIs, the negatives and switching cost may prevent many organizations from migrating from RESTful. There are many articles out there describing pros and cons of GraphQL. The key advantages are that GraphQL let the client-side determine the data it wants while avoiding multiple requests to the server.

GraphQL is a standard promoted by Facebook. There are many ways to implement a GraphQL API, but number of choices in tools, libraries, and frameworks can be overwhelming. There are many good tutorials on getting started with GraphQL in an opinionated way. This post is not meant to be a getting started guide with a preselected set of tools, but rather explore different options that will be on the table during the design and planning phase of a new GraphQL API.

The layers of the stack

Before we dive into different options, let’s go through elements for setting up a production use graphQL system.

  • First layer is a HTTP server to for handle the incoming HTTP requests for your GraphQL server.
  • Second layer, which is typically the core layer, is the query processing, which requires several subparts:
    • Schema definition, done at static time.
    • Parsing and Resolving the query, i.e. determine what actions or methods to take for the each query.
    • Generating and aggregating the outputs.
  • Third, you’ll need to hook it up to a database ultimately, i.e. how to tie the GraphQL schema to your database schema.
  • Fourth, you’ll need to think through the security model and set the right authorization and authentication schemes.

On the client-side, there are couple main elements:

  • Tools and libraries that help you build the requests and processing the return value of the queries.
  • Tools and libraries how to inject the data into your UI, by tying the query to the components of the the UI.

Let’s explore each layer.

Tools for building and defining the Schema

The GraphQL schema itself is language agnostic, it is a DSL (domain specific language) that is well documented here with tutorials. There are a lot of aspects to this DSL, including inheritance, static types, arguments, operators, etc. So learning it and use it effectively might take some time.

A GraphQL query generally looks something like this:

type Person {
  name: String!
  age: Int!
  posts: [Post!]!
}

graphql.js is the official library from Graphql.org

You can just write the DSL yourself, and load it and let it be interpreted by the buildSchema function.

var { buildSchema } = require('graphql');

var schema = buildSchema(
  `
  type Person {
    name: String!
    age: Int!
    posts: [Post!]!
  }
  `
);

graphql.js’s buildSchema is not the only parser out there, there are several, such as graphql-tools by Apollo. The nice thing about graphql-tools is that it helps make modulations easier.

GraphQL Tools enable you to just create a string representation of the GraphQL schema in javascript, which you can read and learn about here, and parses it so that it can be used by other tools.

If you prefer to build the schema programmatically, there are Javascript libraries to help you do it.

import {
  graphql,
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLString
} from 'graphql';

var schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
      hello: {
        type: GraphQLString,
        resolve() {
          return 'world';
        }
      }
    }
  })
});

If you already have an existing project, often times you may already have a schema defined, such as a Mongoose schema for MongoDB. There are people who build tools to generate GraphQL schema from your existing schema. Some are relatively new such as mongoose-schema-to-graphql, while graffitti-mongoose is already outdated. The challenge is that often times GraphQL schema is actually much more expressive than typical mongoose schema, therefore if you do a direct port, sometimes you may not be fully taking advantage of the features of GraphQL. Still, try to migrate any existing product into GraphQL can be a daunting task.

Library Approach Pros and Cons
graphql.js with graphql-tools Write Schema language agnostic
graphql.js Programmatically write schema easier to modularize and prevent errors as you create schema
mongoose-schema-to-graphql.js Generates schema from existing schema The automatically generated schema isn’t flexible enough, as the GraphQL DSL is much more expressive than Mongo schema definitions.

Note It is my personal opinion that using GraphQLSchema, GraphQLString functions to generate your schema “programmatically” seems unnecessary, as the GraphQL DSL itself is very clean, declarative, and language independent. There is no reason to add another layer of complexity. Furthermore, even try to generate schema automatically based on another database’s schema is also unnecessary. If you decide to adopt GraphQL as the backbone of your app, it it is worth a while to think through everything and carefully design the schema, which is the core of your entire application.

Resolvers

Resolvers are a set of functions that corresponds to the data elements of the schema. After the query is validated, the resolvers are triggerd as query is traversed. The resolver the fullfills the data needed or mutations (i.e. updates data in Db) as specified by the schema.

Since the resolvers are just functions, they can take any action in addition to interact with an database. Resolver functions typically looks like this:

Query: {
  human(obj, args, context) {
    return context.db.loadHumanByID(args.id).then(
      userData => new Human(userData)
    )
  }
}

Resolvers are majority of the code you needs to write, include any needed business logic. An analogy would be these are the controllers of your RESTful APIs.

There is no framework can replace your own business logic code, which you’ll have to write yourself, but if most of the data fields are resolved directly to database fields, there can be a lot of boilerplate code which can be scripted.

Note Resolvers can be synchronized or asynchronous. The great thing about Node.js is that it’s already designed for nonblocking IO, and it’s important to take advantage of this. Any network calls such as to another API or separate database fetches should be placed in an asynchronous resolver.

Connect to the data layer

For many common databases like PostgresSQL and MongoDB, there are available drivers and libraries that makes querying easier, help you manage schema, migrations, etc.

You don’t necessarily need to use database drivers designed for GraphQL. However, as mentioned earlier, there are tools that help you generate GraphQL schema based on your database schema. Your application needs may warrant more custom schemas than what a generator can create. Whereas a very simple CRUD app without complex relations could benefit from automatic schema generation.

Prisma takes the reverse route. It lets you create your schema in GraphQL and then generate the corresponding schema in the database you intend to use. It offers a suite of tools for generate those links to database, connect to those database, and offer standard pre-canned code for things like pagination.

Dataloader utility can be used as part of your application’s data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching. Again, even though facebook says it is generic, it is primarily used in GraphQL applications.

Hooking up to the HTTP server

Generally, besides simply hooking up to a HTTP service, the engine actually parses the query, and figures out which resolvers to call. It almost functions as a router, but it does quite bit more, generally engines handles these things also:

  1. Validating Query.
  2. Parsing.
  3. Routing and trigger resolvers.
  4. Put back the results from the resolvers.

The simplest of them all is probably express-graphql, even though it is for ‘express.js’ as the name implies, it actually supports any node based https servers that supports next styled middleware.

To use it is pretty simple:

app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));

Where the rootValue is where the entry point to your resolvers.

You can basically add any sort of express middleware that you are already using such as authentication, or authorizations.

However, there are several other engines or frameworks that offer more features.

Apollo Server is offered by the company behind Meteor. Apollo Server has a simpler interface and supports exactly one way of passing queries. Apollo Server supports more https servers (Express, Connect, Hapi, Koa, Restify) and created separate libraries for each. It is essential part of the suite of tools offer by Apollo (i.e. Meteor). It also factors out ‘GraphiQl’ server, which is more of a dev tool than needed for production.

Graph Cool is also open sourced backend framework for GraphQL, with a strong emphasis on serverless technologies/architecture. Since it is a framework, it does more than setup HTTP server. At the end of the article, I’ll summarize the choices for major frameworks that takes cares of multiple layer of the stack.

Authentication and security in GraphQL

So you created a GraphQL API, but now you need to think over several security issues, especially if it’s accessible from the internet.

For traditional REST API, we wrote an in-depth article here covers some of the key considerations for setup, some of the same consideration is needed for GraphQL. Key difference is that for RESTful API, you can set security requirements at the route level, but for GraphQL API, it’s a single endpoint at /graphql, so you need a tighter coupling with your GraphQL engine for security.

Another consideration for security is that GraphQL is more flexible in building queries, which make it more likely for someone to construct queries that are so complex that they can DDoS your service accidently or maliciously or result in infinite loops that takes up server resources.

Making the queries client-side

Building queries to fetch data is very much similar to JSON. For example, to fetch a human with the id of 1000, and select (project), both the name, and height fields, one would write a GrapQL query like so:

{
  human(id: "1000") {
    name
    height
  }
}

There is an extensive tutorials on the query here.

There are tools for generating and building queries so you don’t have to rely on javascript strings. Query Generator Graphql Query Builder

Since sending the query to the server is simply any HTTP request, you can use any of the popular https client such as SuperAgent or Fetch or Axios, or Request.

While you can manually make the query requests, since in most use cases, the result of the query is to be displayed to the end user, i.e. rendered into an UI. Since there are many frontend UI frameworks out there, there are a lot of choices for helping bind the queries to UIs, those libraries can eliminate needs for manually doing query, also offer key functionalities like cache the data and subscribe to data changes.

Note One great thing about GraphQL is the subscription pattern, which can make the UI experience much better than having the client keep have to fetch the data constantly. However, this make sense for apps like chat, but may not make sense in all scenarios. For example, if user wants to see a table of data, and if the data is constantly re-rendering, it might be annoying. You can let user trigger reload of the data.

Client-side: binding the query to UI.

React, Angular, Vue, and Ember are probably the most poplar frontend frameworks today. If you are starting a project from scratch, it is proably worth a while decide which UI framework first before you chose the GraphQL client tools. Although react these days seems to dominate market share according to Github stars.

Apollo Client offers binders for each framework, plus for Android and iOS as well.

Relay although designed to be very generic, theoretically, it can be used for any UI framework, but it is basically backed by same team that created React. So the libraries and tools are closed tied with react.

There are more in-depth comparisons of these two technologies out there, I’ll quick compare the two technologies.

Technology Server side Requirements UI Framework Compatibility Query Pattern Subscriptions Support Caching
Relay Additional configuration required. Tere are tools that are available. Requires a unique id for every node of the schema. Theoretically any framework, but in practice React More Declarative, i.e. for each component, you describe the data you need and the library will construct the query for you Excellent support. Built in. Guarantees the local store is in a consistent state with the server
Apollo Compatible with Any GraphQL sever. Supports major UI frameworks. Directly builds the queries. Requires additional libraries. Cache works really well in most cases, but you may have to manually perform an updateQueries

In summary, if Apollo client seems a lot easier to learn and get started with, but in the long run Relay is a much more sophisticated system that perhaps worth the investment if your project will likely get very large and complex.

Boilerplates or frameworks

GraphQL by design is not opinionated, but ironically most frameworks and boilerplates are somewhat opinionated.

Given there are so many technology choices for every layer of the tech stack to build your GraphQL based application, especially for brand new app, you might consider where all the decisions have been made, and you can just get up and running quickly, and only replace swap out technology if you absolutely have to. That is where frameworks and boilerplates come in.

  • Apollo have been mentioned several times already. It essentially is a fullstack split the code between server and client, you can use either without being tied to the other side (but of course, it is easier if you use their entire stack).
  • GraphCool focuses on the server side. it tries to stick to open standards, including features such as JWT based authentication, subscriptions, even include thing like Docker.
  • spikenail also focuses on the server side, it is relay compatible right out the box, and it also supports ES7.
  • Vulcan It is full-stack, but it is focused on Meteor as the foundation. Choosing Meteor itself is a major decision for your application that needs to be carefully thought out, since there are lot of pros and cons.

The line between boilerplate and framework sometimes become thinner, but usually boilerplate have even more decisions made for you.

  • Node GraphQL Server is rather minimal boilerplate.
  • nodejs api starter is a boilerplate that comes with the kitchen sink, including database (PostgreSQL) and Docker. So it is most extensive, but might be good to start for beginners.
  • graphql-yoga is another boilerplate that are mostly based on the Apollo stack, such as express-apollo, subscriptions-transport-ws.

Note While picking a framework seems to make decision easy, sometimes you can get bloated with things you don’t need. You can always start with a minimal stack, and then add pieces as you learn more.

Summary

Choosing GraphQL itself as the backbone of your new application can be a daunting task, but after you decided on GraphQL, the downside of it being such an non-opinionated technology, there are so many library and tool choices that we have to make. Sometimes it can feel like decision paralysis. Even if you avoid making a lot of decisions by just adopting a boilerplate or framework, it is worth while knowing all the considerations.

Although any technology, there is no silver bullet, there are some issues with GraphQL as well, such as it becomes much more difficult to debug, especially you have an open public API, you don’t know what kind of query patterns that your API will be used. Analysis of your production API calls can become even more important.

Moesif believes that GraphQL is here to stay, therefore we excited to launch native GraphQL Support on our advanced API analytics platform

Learn More