Step-by-step: Building a Node.js server (2021 edition) — Part 3/4 — The API

Koa.js, GraphQL, MongoDB, Docker, Mocha, Typescript
Koa.js, GraphQL, MongoDB, Docker, Mocha, Typescript

If you want to use the server starter directly without going through the tutorial, find the code on Github. Link to the next parts are at the bottom of this page.

In part I and II we didn’t write much code. Time to write some boilerplate code, and to make our first queries to the MongoDB database through the GraphQL API. There’s no easy way to proceed step by step with this chapter, but everything will make sense at the end.

Requirements: Knowing the basics of GraphQL (Apollo) and of the MongoDB driver for NodeJS.

GraphQL Code-Generator

Let’s install the packages we need for this chapter all at once:

npm i graphql mongodb apollo-server-koa apollo-datasource-mongodb @graphql-tools/schema @graphql-codegen/typescript-mongodbnpm i -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers @types/mongodb

The first thing we need to do when writing a GraphQL API is to define a schema. Let’s create and open src/graphql/types.ts. As our goal is to have a very generic server that can serve any purpose, we won’t dive too deeply into the schema. It is up to you to extend it to meet your needs. However, we can assume that one of the requirements of many apps is to have a basic user management. We will use that for starters and as an example. Our API will expose 2 methods: a GraphQL query to retrieve a specific user from the database from their name, and a GraphQL mutation to insert a user into the database.

GraphQL Code Generator is a neat tool that will generate our Typescript types and MongoDB models, from the schema. It is really nice because it makes the GraphQL schema the single source of truth for our server, API and database. It can even be used to generate some code in the client project (e.g. mobile app, website…).

It only needs a configuration file, in which we will pin the path to the schema file and list the plugins we want to use. In order to generate MongoDB models, we need the “typescript-mongodb” one. Note, it is thanks to the @entity attribute in the schema file that the plugin knows User is a collection in the database. Add a codegen.yml file at the root:

Add the npm run codegen command in the package.json file and run it to generate the types in src/generated/types.ts:

"codegen": "graphql-codegen --config codegen.yml"

You may want to add the src/generated folder to the .eslintignore file.

Connect to the Apollo Server and Database instance

Now that we have our database and API types let’s take a break to see how we will build our server.

The code architecture
The code architecture
Class diagram of the server

We will instantiate an ApolloServerManager, which will handle the GraphQL schema and resolvers. It will also use the DatabaseManager we pass it to connect to the MongoDB instance. We will abstract the DatabaseManager to be able to provide a different database for our tests (we will take care of that in part IV, don’t pay it attention for now. It is only here to understand why the abstraction). We can delegate the ownership of the DatabaseManager to the ApolloServerManager because Apollo provides a “Data sourcing” mechanism, so our Apollo server will directly use the database helpers we provide.

It can be a little bit difficult to visualize so let’s jump into the code!

Database managers

Create a src/DatabaseManager.ts file:

  1. We’re using the MongoDB driver for NodeJS to instantiate a MongoClient. The methods are pretty standard lifecycle methods, to start and stop the client. We can’t really connect to the client in the constructors because the connect() method is async. So we need to call a dedicated start() method manually.
  2. To build the connection string needed by MongoDB to connect to our database container, we’re using the environment variables we defined in the .env file.

Apollo Server manager

Time to write our GraphQL API.

We need to define some CRUD methods to interact with the database (they’re called Datasources by Apollo). They will be called by our GraphQL resolvers. Create a src/datasource/User.ts file:

  • For the findOne method, we are basically encapsulating MongoDB’s NodeJS driver’s findOne method to fetch users with a query.
  • Same or the insertOne method, except that instead of a query we’re passing arguments (the new user) to MongoDB’s insertOne method.
  • Note that we’re importing the UserDbObject from our generated types. 😎
  • There’s some basic error management but feel free to add more checks.

We then need to define GraphQL’s resolvers and typedefs, in src/graphql/schema.ts:

  • The DIRECTIVES declaration is needed by the code generator to properly map the entities. That’s why we saved this package in the regular dependencies (it is needed at runtime).
  • Our GraphQL clients will be able to query user(name: $name) and to call the addUser(name: $name) mutation.
  • We’re using the datasources we defined in src/datasource/User.ts to link our GraphQL API to the database and return something to the clients.

Last file I promise! Now comes the actual ApolloServerManager (src/ApolloServerManager.ts):

  • Line 24, we’re instantiating the actual ApolloServer. We are passing it our schema, a context pre-populated by Koa, the datasources and an error handler callback.
  • Ideally we would add authentication and populate some loggedUser field in the context, but that’s out of this tutorial’s scope. See Apollo’s tutorial for how to do that.

Run our GraphQL and MongoDB powered back-end

That was a lot. All that’s left to do is to instantiate an ApolloServerManager and an AppDatabaseManager in the src/server.ts file:

We’re instantiating the ApolloServer with introspection and playground on for development, so that we can use GraphiQL to run test queries and mutations against our server. Don’t forget to also add the graphql routes.

Let’s see if everything runs fine:

Run docker-compose up -d database mongo-express and npm run start. Head over to http://localhost:$SERVER_PORT/graphql and see if the GraphiQL playground appears. You can then run the addUser mutation:

# Write your query or mutation here
mutation AddUser($name:String!) {
addUser(name:$name) {
_id
name
}
}

Add the following variables in the “Query Variables” area at the bottom:

{
"name": "Batman"
}

If GraphQL effectively returned the inserted user after running the mutation, head over to http://localhost:8081 (our mongo-express service) and check the database/users collection. Yay, we have a Batman! 🦇 You can also query the Batman through our API:

# Write your query or mutation here
query User($name:String!) {
user(name:$name) {
_id
name
}
}

Congrats, we’ve built a good base for the server of any business application. We’ve added many features and kept everything super generic, so you could in theory stop here, duplicate this starter for each new project and build on top of the very basic user API we built with GraphQL and MongoDB.

If you’re interested in seeing how we can test the whole infrastructure, click the link below to head over to part IV! ⏩

Written by

Software developer — theopnv.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store