Writing a Node.js GraphQL backend that actually scales — A complete guide

Part 1: Setup

Ernesto Garcia N
JavaScript in Plain English

--

In the web development world, whenever you’re going to build a modern application, you have a lot of tools and frameworks to choose from to build your stack, and usually, people take whatever they feel comfortable with, or the newest/popular technology.

If you’ve been in this situation before, you know that GraphQL is a popular alternative for building your backend application, and you’re maybe curious about it.

Well, I was curious back then, and after some years and many projects professionally developed with it, I can assure you that GraphQL is fantastic when done the correct way, which I worked so hard to figure out.

Probably you already know about it. There are lots of tutorials and youtube videos teaching you how to build a Node.js backend with GraphQL Apollo Server, and maybe you’ve looked to one of those. Or maybe not.

The ultimate objective of this series of articles is not only to show you how to build it from scratch in a proper manner but to demonstrate to you how you can scale to actual an actual large-sized code base.

Even though GraphQL has proven itself as a mature and reliable technology to build the backend of your application, I think it hasn’t increased in popularity as it should. People still rely on the old, but well-known RESTful model APIs, because it's still usable and they usually consider GraphQL an overengineered solution, although the consensus is positive about it.

Source: https://2020.stateofjs.com/en-US/technologies/

Seems like developers, for some reason, don’t really like to build GraphQL backends but they actually like to use them. Well, I’ll try to show you a way in which I’ve been building GraphQL backends in a successful way, in a series of articles.

Take this as the article I might have wanted to have when I started using graphql.

You’ll learn how to…

  • Set up an Apollo Server with Express (trust me, you’ll need it) using Node.js from scratch
  • Build query structures that allow you to cover many use cases while still testable and reliable
  • Write directives
  • Structure and split your code, so it won’t become a massive chunk on a single file
  • Model your data in a proper way and connect it to a database using an ORM/ODM
  • Optimize your operations to avoid duplicated queries to your database using dataloaders
  • Take serious advantage of using and configuring dynamic calculated fields via your schema

You won’t learn how to…

  • Basic understanding of GraphQL types and resolvers. I’ll assume you already know that but you want to know how to improve your project. The tutorial will be for beginners, but there are other better resources to learn about primitive types.
  • Handle authentication (that is for another whole article)
  • Handle file uploads (same as the previous one)
  • Linting and code style (again, for another article)
  • Add tests to your schema and resolvers (you know… for another article)
  • Build your own data types
  • Deployment

Note: Everything up to this part of the tutorial can be found on my public repo

Let’s get started

The first thing you gotta do is to create a new folder with and start a new npm project, you can do it by running the following:

mkdir graphql-backend
cd graphql-backend
yarn init -y

We’re using the-yflag to avoid configuration settings, but you may remove it if you want.

For this tutorial, we will work with the newest Javascript syntax at the moment, and this will be achieved with Babel, which is a transpiler that takes our Node.js modern code and converts it to something compatible with the basic node syntax. We won’t dive into it but it’s good to know what is it for.

Also, to have hot reload to immediately see changes in our application, you may want to add nodemon, which will automatically restart on every update.

In order to add the mentioned tools, just run:

yarn add @babel/core @babel/node @babel/preset-env nodemon -D
  • @babel/core: This is the core library
  • @babel/node: This will allow us to run our code using babel-node command instead of the traditional node command.
  • @babel/preset-env: And finally, this one is a preset with the most recent additions to the language, so you don’t have to worry to manually add new syntax

Important: In case you’re planning to create a production build for deployment reasons (which we’re not covering as previously said), you should add -D flag, so they are only added for the development environment.

Folder structure

Once you’re done with our basic setup, you may have the basic files in your folder (such as yarn.lock or package.json). The next step is to add a basic structure for which we will be expanding in the future, but for this part 1, is enough:

I’m listing the reason to have each file aside from the graphql folder:

  • index.js: We’re creating an index.js at the root level so we can use it to start things up, this is pointless at this moment due to our project size, but it’ll be useful when we start adding database stuff, and for you in the future when you may need to add extra capabilities outside of graphql to your application.
  • app.js: This will be the configuration file for the express app, which we’ll initialize on the index.js.
  • config: A configuration folder is useful to centralize the general settings of your application, we’ll also expand on that later, but at this moment we will create an environment folder to consume environmental variables there.
  • .env.*: Our env files will work to specify the environmental variables to make our app behave differently in each environment, depending on how we want it to work. The .env is ignored via gitignore and the .env.example is kept to have a template of the environmental variables needed. Useful if you work in a team
  • .babelrc: Configuration file for Babel

Now the interesting part: inside the graphql folder, we will separate our schema and resolvers into folders, because we will see how to split our code into different files depending on the resources we’ll model. This will allow you to grow your codebase in a comfortable way.

Basic dependencies

Next, we will add our basic dependencies to get our project to work:

yarn add express apollo-server-express graphql @graphql-tools/schema dotenv
  • express: It is a minimalistic HTTP server, but you probably already know that. We’re using it because trust me. It is useful to achieve things on your application that shouldn’t be handled by graphql. Usually, you need to expose endpoints for other purposes rather than data accessing, like health checks or authentication.
  • apollo-server-express: Since we’re using express, it is needed to use the express compatible version of Apollo.
  • graphql: Even though it is not directly used on our application, it is a peer dependency of several libraries, so you have to add it.
  • @graphql-tools/schema: We will use this to build and compile our schema
  • dotenv: In order to make your application suitable to work on different environments, you may want to add environment variables. We will read them using this dependency

Setting up the server

For now, let’s just add the straightforward code to make our express app work before we dive into the graphql folder configuration. Just copy the following code into the corresponding file, so we can have a working HTTP server.

First, create a basic express app in app.js file:

Then, configure your .env.example with your environmental variables template

And now, create a copy of it in .env (remember this is hidden), and configure it as you want. I’m adding the setup I’m using:

Then, in your config/environment/index.js file, read those environmental variables, and export them. This is a great way to have your configuration centralized and then, use it on different components of your application:

Now, it’s time to configure your .babelrc file in order to use the preset-env we talked about before

Once done, you only have to initialize your app on your index.js

And finally, add an npm script to your package.json , so you can run the whole thing:

nodemon ./index.js — exec babel-node

Then, just run your server and check that everything is working with:

yarn dev

You should see something like this:

Setting up our schema

At this point, you have a basic setup running an express (not graphql yet) server, which you can use to configure the main things you might need in the future. Even though it may seem like there are a lot of folders and files, it will make sense as long as we continue building our application.

From now on, we’ll dive into the details of creating our graphql server inside the graphql folder, but eventually, we’ll add some extra folders in the future to connect with our database, but for now, let’s explain how we’re going to prepare our graphql folder structure to scale correctly.

First of all, we need to prepare our Apollo Server inside the graphql/index.js:

You’ll notice that we are using our env variable exported from our centralized environment. This is what is it good for. You’d like to define different behaviors depending on the environment for the future. In this case, we are locking the playground to only make it work in our development environment.

Also, according to the official apollo docs, you’ll find that we need a schema defined by two main things:

  • Typedefs: The shape of our data
  • Resolvers: The way data is calculated and returned

Well, usually tutorials explain how to configure these two guys, but we’ll take a step further and split them, so we can have a well-organized code base since the beginning.

As you previously saw, we defined two folders within the graphql main folder. These two will help us to divide our code. But first, let’s see what data we’ll model:

Modeling data

Entity-Relationship model

It is an extended version of the typical book-author example, but this has some extra little things that we’ll use to cover deeper concepts in the future.

Let’s see, in general, our main entities are:

  • Publisher
  • Author
  • Book

The WorksAt entity won’t be exposed via graphql since it is only a weak entity, and it only exists to create a many-to-many relationship, so we’ll not consider it as a queryable field on our schema.

Typedefs

Now we have a well-grounded understanding of how our data shaped, let’s start creating our typedefs in the following different files:

You’ll see that we have a .graphql file for each of our entities to model, along with an index.graphql and a common.graphql. We’ll use the index.graphql to hold our Root Definitions (read as Query and Mutation) and the others for specific entity-related types. The common.graphql is for general-purpose types.

On our index.graphql we will fill with the following:

You’ll notice the Date scalar. This has to be declared because it is not a native type of graphql, but it is included with @graphql-tools, and it’ll work just with this declaration.

Note: We won’t be handling deletion operations because this creates other data reliability problems that we can address with techniques that are not part of the scope of this tutorial.

Now, let’s fill up our entity graphql files with the missing data types:

On our common.graphql we’ll define an interface to apply to every single entity since every one of them has its corresponding timestamps.

For our author, we will define the types for the name and id specified on the database, along with the Timestamps interface. Also, we’re going to relate this with publishers, but we’ll see how to resolve this relation in the future

For our book, is pretty straightforward. The relation with the author is much simpler so we might address that later.

Finally, the publisher is pretty much the same situation as with the author:

Now we have our entire schema split into different files. The question now is, how will we put all of this together?

Well, we are going to create a script to process every single file inside the typedefs schema. This will allow us to create a final version of the schemas we have, to construct a single schema.

Go to your project and create a schema.js next to the graphql/index.js file, which is where we’ll process everything:

Inside of it, we will use node filesystem utils to read the name of the files, then, convert them to a string, concatenate them and finally put them together using @graphql-tools/schema.

Resolvers

At this point, we have our typedefs completed, but we need to fill those typedefs with data using the resolver functions. These are going to be built inside the resolvers folder in a sub-folder of the name of the entity we’re resolving. So, let’s create the following structure, and then, we’ll dive into the reasons for organizing info like that:

You’ll see that each of the folders have the same structure, and this is why:

  • index.js: We will use this file to mount code in the future to resolve that single entity. You might find this useful when we optimize our queries. For now, it will just expose the default exports from mutations and queries files.
  • mutations.js: We will put every function that resolves mutations here
  • queries.js: We will put every function that resolves queries here

In regard to how does each file looks on the inside, here you have an example for the author folder:

queries.js:

mutations.js:

index.js

Note that we’re not even resolving anything at this point. This is because we’re going to fill the data in the next article with actual database data.

From now, I think it’ll be clear how to fill the remaining files, so I’ll let you do it.

The main reason for doing all of this splitting into files, is because we’re aiming to have a scalable application. So, even though it might seem like everything is separated. I assure you this will keep your code correctly organized into topic-related files and will give enough space to expand in the future.

Let’s finally merge these resolvers into a single object in your graphql/resolvers/index.js file.

Now, we just have to add our resolvers to our graphql/schema.js file, so they can work together

Then, use this fresh-new schema and add it to your Apollo Server in graphql/index.js

And to wrap things up, just add your express server as a middleware to your apollo server:

And, if everything is working well, you’ll see the same message on your terminal as before, but you can actually check your schema at http://localhost:3000/graphql:

Take into consideration that you will have errors if you request data at this point since there is no way that resolvers can parse and return whatever you’re requesting, but we’ll fix that in the next article.

Wrap up

In this part 1 of the tutorial we:

  • Created a basic setup for a node project
  • Added support for Babel in order to use modern syntax
  • Created a config file to read environmental variables, so we can use them to manage different behaviors depending on the configuration
  • Set up a basic express server
  • Set up a graphql server with our express server as a middleware
  • Modeled data
  • Defined a schema to serve the data we modeled in a well-organized bunch of folders and files
  • Combine every single file into our main schema

Remember, everything at this point can be consulted and reviewed into my public repo for the entire series, which you can find here.

Part 2 here

Read more at plainenglish.io

--

--

Ethereum Developer @OpenZeppelin | Intern twice @Google | Blockchain Development Teacher @blockdemy and @platzi