In this post we’ll have a look at leveraging GraphQL types to expose MongoDB capabilities in NodeJs. We will also examine graphql-to-mongodb, the unobtrusive solution we came up with for our service, and its rationales.

The advent of the GraphQL MongoDB stack

Relative newcomer GraphQL and established MongoDB are two technologies that appear well suited to one another. MongoDB is a document-oriented DB with a flexible query language. GraphQL is a service API and a query language at the same time. It works by defining a hierarchical, typed, and parameterized schema. Both technologies take and receive arguments in a hierarchical data structure. Taken together, the two compatible technologies can make for a streamlined interface between your client and data.

First off, if you feel that your knowledge of either technology could use a refresher, you’re welcome to read more about GraphQL or MongoDB before continuing.

Growing pains

Implementing a way to expose MongoDB querying on a GraphQL backend is not a trivial task; we learned that when we tried to join the two in our latest NodeJs service. It’s easy enough to start by adding a single query field, using a single comparison operator, one at a time. As the query complexity of your clients increases, however, you can easily find yourself maintaining a disorganized mess of filtering code.

A developer might be tempted to simply accept a generic JSON type as input, passing a client’s input directly to MongoDB, but we saw that kind of solution to be less than satisfactory. Not only does that approach miss the entire point of GraphQL, it also gives up control over how the client may communicate with the DB.

Our ideal API

Upon recognizing that the issue was less than simple, we set out on a search for a solution that suited our needs, which were:

An interface to MongoDB’s powerful querying capabilities

Simple implementation

Unobtrusive integration

Explicitness and consistency to the GraphQL type schema

No vulnerabilities to NoSQL injections

Unfortunately, our search yielded less than fruitful results.

If you want something done right…

As is often the case, we couldn’t find a mature and well-documented third-party solution that met our needs, prompting us to design one ourselves. Subsequently leading us to come up with an answer in the form of the package graphql-to-mongodb, publicly available on both GitHub and npm. Fundamentally, the package works by generating query arguments for your GraphQL schema at run-time, based on your existing GraphQL types. It parses the sent requests into MongoDB query params.

Let’s explore how it checks off the needs we identified earlier:

MongoDB for your client

The package boosts your GraphQL API with the bulk of MongoDB’s most commonly used query operators. With it, a client can comb through the underlying data in a multitude of ways without requiring additional changes to your API for every new query.

An example GraphQL query sent to a service using the package, showcasing filtering, sorting, and pagination:

{

person (

filter: {

age: { GT: 18 },

name: {

firstName: { EQ: "John" }

}

},

sort: { age: DESC },

pagination: { limit: 50 }

) {

name {

lastName

}

age

}

}

Queries 50 people, oldest first, over the age of 18, and whose first name is John

All of that, and more, for a very small development overhead in your service.

Simplexity

As with many packages, it strives to give you the biggest bang for your buck, hiding the complexities of the solution behind a simple integration. The exposed GraphQL field will be based on your underlying GraphQL type describing the data structure schema.

Given a simple GraphQL type

When implementing the package, for the most common use case, all you need to do is to build a field in the GraphQL schema with a wrapped resolve function (getMongoDbQueryResolver) and generated arguments (getGraphQLQueryArgs).

We’ll add the following field to our schema

That’s it!

For the price of two function calls, you’ve just added all of the functionality described in the previous section to your API.

The additional arguments supplied by the wrapper — filter, projection, and options — can be passed directly to MongoDB! To get an idea of what the package does, take a look at these arguments, produced from the previous section’s query:

It’s just middleware

It’s clearly visible that the package behaves like a middleware. This feature allows for the development of modules independent of MongoDB in the GraphQL service.

Fields built using the package’s function can be easily extended. It’s simple enough to merge additional arguments into the generated args object, and to add handling in the resolver.

Resolving fields within your GraphQL type is also supported, though it requires a minor overhead in defining the field. One of the added benefits of the package is a minimization of throughput by projecting from MongoDB only the fields requested by the user. For resolved fields, what that means is that their dependencies might not always be queried from the DB. To resolve that issue, the package allows you to define a resolve field’s dependencies to ensure that when that field is queried, its dependencies will always be retrieved as well.

Alternatively, if throughput is of no concern, the projection argument supplied by the resolve wrapper can simply be discarded and replaced by an empty object.

Well-defined…

Because the functionality of the package is based solely on the GraphQL types of your implementation, the exposed API of the service is both explicit and consistent.

Because of course there’s mutation functionality…

Only the fields defined in the original GraphQL type (to the far left above) are exposed as arguments in the schema fields. Likewise, generated input and insert types provided as an additional functionality of the package are derived directly from your original type and grant mutation capabilities on its fields to your API.

…and safe

The explicit nature of the API provides it with a measure of security. GraphQL provides out-of-the-box input validation, ensuring that all arguments match the types defined by your schema. With every one of your fields being unambiguously defined and consistently processed, would-be attackers are left with no wiggle room to exploit, or human errors to target.

Give it a try

Next time you’re designing a new service, or just considering an overhaul of an existing one, reflect on the benefits and principles of the package. If you want to give your NodeJs GraphQL service a whole lot of the power of the MongoDb database you have standing behind it with very little hassle, mayhaps you’ll consider adding graphql-to-mongodb to your implementation.