GraphQL.js vs. TypeGraphQL vs. GraphQL Nexus

Matt Lim
The Startup
Published in
13 min readSep 28, 2020

--

When building a GraphQL server, one of the first decisions you must make is which GraphQL implementation to use. There are three popular options.

  1. GraphQL.js — This is the reference implementation for GraphQL. GraphQL schemas are built by composing objects created from classes such as GraphQLObjectType, GraphQLInterfaceType, and GraphQLEnumType.
  2. TypeGraphQL — This implementation builds upon the reference implementation and adds built-in TypeScript support. It makes it easier to make schemas typesafe. GraphQL schemas are built by creating classes and decorating them and their methods with TypeGraphQL decorators.
  3. GraphQL Nexus — Similarly to TypeGraphQL, this implementation builds upon the reference implementation and adds built-in TypeScript support. However, the approach taken is much different. Similarly to the reference implementation, GraphQL schemas are created by composing objects created from classes such as objectType, interfaceType, and enumType. Unlike the reference implementation, Nexus uses some black magic TypeScript trickery so that your schemas are more strongly typed. Unfortunately, as we’ll see below, this doesn’t always work so well.

In order to do a proper comparison of these three implementations, I’ll show how you can build the Star Wars schema using each of them and talk about their pros and cons. At the end, I’ll give my opinion on which one I prefer using.

The code shown in this article can be found here.

GraphQL.js

Overview

This is the reference implementation of GraphQL, meaning it implements the full GraphQL specification.

It is well maintained — it has a steady flow of commits, and most issues have a response. Since it is the reference implementation, we can be confident that it will continue to be well maintained.

Documentation can be found here. The documentation is decent, although since the implementation’s API is relatively simple and there’s a good example to follow, I haven’t found it that necessary.

Star Wars Schema

Let’s take a look at how the Star Wars schema can be implemented using GraphQL.js. We won’t look at the entire schema, just a few representative parts. Remember, the full source code for these examples can be found here.

Here’s how you define the GraphQL schema using GraphQL.js. query represents the Query GraphQL object type.

Here’s how you define the Query object type using GraphQL.js. You create an instance of GraphQLObjectType and define the name and fields.

Here’s an example of one of the query fields. Fields will typically define type, which determines the GraphQL type of the field, args, which determines the arguments the field takes, and resolve, which is a function that produces the field’s result. Overall, this corresponds to the following SDL:

type Query {
droid(id: String!): Droid
}

The resolve function is strongly typed: it returns an object that has type Droid. However, note that the type Droid is explicitly defined by us, separate from the GraphQL schema definition. TypeGraphQL and GraphQL Nexus make it more convenient to strongly type code like this.

This is the object we used as the type of droidQueryField. The resolvers defined for these fields will take the object returned by the resolve function of droidQueryField as their first argument. That is, the droid in getFriends(droid) is the value returned by getDroid(id!) that we saw above.

Pros and Cons

Pros

  1. It’s well maintained.
  2. It’s well documented.
  3. It’s guaranteed to implement the full specification (ignoring bugs). TypeGraphQL and GraphQL Nexus use GraphQL.js under the hood, but may impose additional limitations. For example, TypeGraphQL does not let you add descriptions to individual enum values. GraphQL Nexus does not let you modify the description of a field that was originally defined on an interface.
  4. It has a small number of dependencies (just install graphql and you’re done!).
  5. It’s simple to use and understand. It basically boils down to composing different GraphQL___ objects.

Cons

  1. If you want type safety, you need to manually define the types. For example, if a resolver returns some object, you need to define the type of that object. You don’t get the type for free like with TypeGraphQL.

TypeGraphQL

Overview

This is an implementation of GraphQL that builds upon the reference implementation. Most of its features are implemented using TypeScript decorators, which are an experimental feature.

TypeGraphQL is well maintained — it has a steady flow of commits, and most issues have a response.

Documentation can be found here. The documentation is good — it’s quite thorough, and contains a large number of useful examples. There’s also a good YouTube series by Ben Awad that I found useful when learning this library.

Star Wars Schema

Let’s take a look at how the Star Wars schema can be implemented using TypeGraphQL. We won’t look at the entire schema, just a few representative parts. Remember, the full source code for these examples can be found here.

In order to build a schema using TypeGraphQL, you pass in your top-level resolvers, which should correspond to fields on the Query or Mutation objects. Note that validate: false must be specified as well, since we are not using class-validator.

In order to define a field on the top-level Query object, you should annotate a class method with @Query. The class itself should be annotated with @Resolver. You could have a single QueryResolver class, and then define all the Query fields in that class as separate methods, but people typically split their code into separate resolvers and/or modules. This makes dependencies more explicit and ensures that there’s not one huge file that is hard to navigate.

The droid method is strongly typed. Remember that the resolve function we saw above was strongly typed too — the difference here is that we do not have to redefine a “droid type” in TypeScript in order to gain type safety. Instead, we just use the Droid class, which also serves as the GraphQL object definition. Keep reading for more details about the Droid class.

Here’s the Droid class that we used in DroidResolver. This is analogous to writing const droid = new GraphQLObjectType({…}) in GraphQL.js. Fields are defined by annotating JavaScript class fields with TypeGraphQL’s @Field decorator. You can also use this decorator to provide field descriptions and alter the types of fields (e.g. make them nullable).

If we didn’t want to modify the descriptions of Droid's fields, we could’ve extended the Character class instead of implementing it. Since fields are inherited in JavaScript, we wouldn’t have to redefine them in Droid, and we could cut down on some code.

There are two things I don’t like about this class.

  1. It’s often necessary to define a constructor that converts a plain JavaScript object into an instance of your class. In this case, it’s necessary because the data is defined as plain JavaScript objects, and the types of those objects are not compatible with the types of the classes. For example, the droid objects don’t have the friends or secretBackstory fields. If you use an ORM like TypeORM, you might not run into this problem, because fetching some data will return an instance of a class. As a sidenote, I don’t like tying both the database schema and the GraphQL schema to the same classes, as I find it leads to unnecessary coupling. That’s why I prefer Prisma to TypeORM (Prisma returns typed objects when you run a query, as opposed to an instance of a class).
  2. The Droid class contains class fields that are associated with GraphQL fields (all the fields decorated with @Field) in addition to a class field that is not associated with any GraphQL field (friendIds). The latter class field exists so that the friends inline field resolver works. I prefer the approach taken by GraphQL.js, which decouples the data that is returned from a field’s resolver function (e.g. a Droid object) from the data that is ultimately returned once the field has been recursively resolved (e.g. the droid object type). In TypeGraphQL, this data is all stuffed into one class.

In general, I prefer working with plain JavaScript objects rather than classes. TypeGraphQL features classes heavily, which makes some things easier, but can also lead to more complication.

Pros and Cons

Pros

  1. It’s well maintained.
  2. It’s well documented.
  3. It reduces the number of types you need to manually define, because you can use the classes you decorate as types in other parts of the code (e.g. as the return type for a resolver).
  4. The decorator pattern is fairly flexible. For example, you can decorate one class with both TypeGraphQL decorators and TypeORM decorators. You can also use class-validator decorators. I’m putting this as a pro because some people like this flexibility, but I don’t recommend reusing the same class for both TypeGraphQL and TypeORM. I think this leads to too much coupling between the GraphQL schema and the database schema, which should be different in most cases.

Cons

  1. It makes heavy use of classes, which may be a non-ideal model if your data is represented as plain JavaScript objects.
  2. It doesn’t decouple the data that a resolver function immediately returns (e.g. friendIds) from the data that is returned once all resolver functions have been recursively called (e.g. friends).
  3. Decorators are complicated. First of all, it’s non-trivial to understand how they actually work. Secondly, it’s easy to add functionality using decorators, but reading the resulting code can be tricky. When decorators on a method or class start stacking up, it becomes harder and harder to reason about what is going on.

GraphQL Nexus

Overview

This is an implementation of GraphQL that builds upon the reference implementation. Code written using GraphQL Nexus looks similar to code written using GraphQL.js. The main differences are that Nexus is a bit more concise and plays more nicely with TypeScript by inferring the types of GraphQL arguments, objects, and more.

GraphQL Nexus is the least well maintained out of these three. The commit history is fairly sparse, and there are a large number of issues without a response. Apparently the Prisma Labs team is helping out with development and maintenance, which is at least good to know (I’d rather it be maintained more “officially” than by some lone person as a side hustle). However, when compared to GraphQL.js and TypeGraphQL, GraphQL Nexus still falls short.

Documentation can be found here. Unfortunately, it’s not very thorough (it says as much here), and doesn’t do a great job of explaining how the type inference works. I wouldn’t mind this latter part as much if the type inference worked better, but I’ve found a number of issues with it.

Star Wars Schema

Let’s take a look at how the Star Wars schema can be implemented using GraphQL Nexus. We won’t look at the entire schema, just a few representative parts. Remember, the full source code for these examples can be found here.

Here’s how you build a schema with GraphQL Nexus. The types field is pretty flexible — here, Query is an object that contains all the query fields.

The outputs field determines where the generated schema (SDL) and schema types (TypeScript) are exported. This is quite nice — it’s very convenient to be able to see your GraphQL schema in SDL just by switching over to another file (as opposed to using Apollo’s GraphQL Playground). It’s also nice to be able to check this file into source control. Note that these files get automatically regenerated whenever any relevant part of the code changes.

This code defines the Query object’s droid field. It’s pretty similar to GraphQL.js, just a bit less verbose and with some nice type inference (which unfortunately doesn’t always work).

Notice that even though GraphQL Nexus should provide some automatic type inference (e.g. it should infer the types of resolve's parameters, although for some reason it doesn’t), it doesn’t account for everything. That is, the return type of the resolve function is Droid. That type doesn’t get automatically generated or inferred, we have to define it manually.

This case is a bit special because the friend field of Droid has a non-trivial resolver, i.e. it does more than just read the same-named property of the object. This means that the type that is automatically generated for droids doesn’t match with what we return from resolve. If all of Droid's resolvers were trivial, then we could use one of the types that GraphQL Nexus automatically generates.

The type inference doesn’t always work.
Sometimes it works (code taken from https://github.com/graphql-nexus/schema/blob/develop/examples/star-wars/src/graphql/query.ts).

This code defines the GraphQL interface Character.

The Character interface is used when defining the Droid object type. You don’t have to rewrite the fields, which is nice. However, I couldn’t find a way to modify the descriptions for Droid's fields, like we did with GraphQL.js and TypeGraphQL. In most cases, this is probably not a huge deal.

Pros and Cons

Pros

  1. It generates the GraphQL schema (in SDL) whenever changes are made to the code.
  2. The syntax is simple, and very similar to GraphQL.js. No experimental features like decorators are needed.

Cons

  1. The documentation can use improvement.
  2. It’s not as well maintained as GraphQL.js or TypeGraphQL.
  3. The type inference is buggy, and doesn’t always work.
  4. If you want complete type safety, you’ll still have to manually define some types. GraphQL Nexus won’t take care of everything for you.

GraphQL.js + GraphQL Code Generator

Overview

Wait, aren’t we done? Not so fast — there’s one more option I want to cover. By combining GraphQL.js with GraphQL Code Generator, you can get the best of both worlds — a simple and reliable GraphQL implementation with automatic type generation.

GraphQL Code Generator lets you generate TypeScript types for a GraphQL schema via the CLI or programatically.

It is well maintained — it has a steady flow of commits, and most issues have a response.

Documentation can be found here. The documentation is solid.

Star Wars Schema

The full source code for these examples can be found here.

Implementing the Star Wars schema with GraphQL.JS and GraphQL Code Generator is extremely similar to implementing it with just GraphQL.js. The main difference is that GraphQL Code Generator generates TypeScript types which can be used to cut down on manual type definition. We’ll take a look at how that works now.

The performAstCodegen function generates generated.graphql and the performTypeScriptCodegen function generates generated.ts (refer to index.ts to see how these functions are called). The former file is mainly useful as a reference — for example, instead of looking at Apollo’s GraphQL Playground to view the schema, you can simply open up generated.graphql. The latter can be used to strongly type your code.

This demonstrates how to use GraphQL Code Generator programatically, as opposed to using the CLI.

Here, we see an example of using the generated types to strongly type our code. Before, we would’ve had to manually define the Episode enum or type the input as a number. With GraphQL Code Generator, we can simply import the desired type and use it.

It’s important to note that we still need to manually define some types. For example, the autogenerated Human type does not accurately describe the data that is returned from human's resolver function. Specifically, the friends property differs, as you can see below.

Pros and Cons

Pros

Same as the pros for GraphQL.js, in addition to:

  1. GraphQL Code Generator generates TypeScript types, cutting down on the amount of manual type definition needed.
  2. GraphQL Code Generator generates a SDL file, which allows you to easily view the schema and check it into source control.

Cons

Same as the cons for GraphQL.js. There are no additional cons.

However, you should note that some manual type definition is still necessary, as we saw above.

So, which one should you use?

GraphQL.js is my winner.

I personally prefer GraphQL.js over the other two, with or without GraphQL Code Generator. Adding GraphQL Code Generator only makes things better, so I recommend using GraphQL.js + GraphQL Code Generator. TypeGraphQL cuts down on some manual type definition, but I don’t think that benefit outweighs the additional complexity it introduces. GraphQL Nexus also cuts down on some manual type definition, and is supposed to infer the proper types so you don’t have to write them. However, it’s buggy and not very well maintained or documented. If GraphQL Nexus worked perfectly, I’d pick it over GraphQL.js — unfortunately it does not.

With GraphQL.js, you’re getting a well maintained and well documented implementation that is simple and reliable. The main tradeoff is that it necessitates manually defining a few types. This is inconvenient, but makes the code simpler. For example, you can define a type that represents exactly what a resolver function returns, as opposed to mixing together fields that should belong in different types like TypeGraphQL does. This tradeoff can be mitigated by combining GraphQL.js with GraphQL Code Generator.

If you don’t want to use GraphQL.js for some reason, I’d pick TypeGraphQL over GraphQL Nexus. The typing is much more reliable, and it’s better maintained and documented. I wouldn’t consider GraphQL Nexus for anything other than some small side projects.

Ultimately though, it’s up to you! I recommend actually trying each of these options out before making a decision. The repositories I listed at the top of the page should help you do that. Finally, if you have an opinion on this, leave it in the comments. I’m curious to hear other thoughts on this subject!

--

--

Matt Lim
The Startup

Software Engineer. Tweeting @pencilflip. Mediocre boulderer, amateur tennis player, terrible at Avalon. https://www.mattlim.me/