Get Started with Node.js and Express
Tags: node, express, programming
Published: 2021-07-30 18:27:51

---
title: Get Started with Node.js and Express
published: true
date: 2021-07-21 19:19:52 UTC
tags: node, express, programming
canonical_url: https://www.split.io/blog/get-started-node-js-express/
---

Starting up a server with a RESTful API takes only a few minutes. With tools like Node.js environment and Express – a Node.js web application framework – this could be done in a very simple fashion. This tutorial will bring you one step further – to understand why it’s important to write decoupled server-side code.

Some of the topics covered in this tutorial are:

- Create a server-side web application in Node and Express using an in-memory database
- Implement a simple REST API following best practices with controllers and services
- Expand controller with adding [feature flags branching](https://www.split.io/blog/branch-by-abstraction/) with Split
- Move the feature flag logic to a better place than a controller – a service

And, no worries, the app you’ll build will definitely rock, as it will be about famous rock albums!

## Prerequisites

To follow this tutorial, you should have:

- Basic knowledge of [Node.js](https://nodejs.org/en/docs/) and [Express](https://expressjs.com/en/4x/api.html)
- Basic familiarity with the command line
- Node.js and npm installed
- [HTTPie](https://httpie.io/docs) – a modern replacement for curl
- A [Split](https://www.split.io/signup/) account

If you’d like to follow the codebase while reading, the full code example is available on this [splitio-examples GitHub repo](https://github.com/splitio-examples/modular-node-express-example).

## Setting Up the Express Server

Start by creating a project directory. Then move to its root folder:

```
mkdir node-express
cd node-express
```

Set up the `npm` project by running `npm init`, as that will create a `package.json` file for you. An alternative is to copy this JSON structure to the `package.json` that you can create on your own:

```
{
  "name": "node-express",
  "version": "1.0.0",
  "description": "Get Started with Node and Express",
  "main": "app.js",
  "scripts": {}
}
```

Proceed to install Express.js, a popular server-building package in the Node ecosystem.

```
npm i express
```

Create an `app.js` file in the root directory where the server application is intended to run. Notice below how the `express` module is imported in the file to create a server. Part of the code containing `app.use(express.json())` is using middleware to parse the requests that you’ll be sending later in this tutorial. This way the server knows how to read the requests of the `application/json` content type. As configured below, the server is running on port `3000`, meaning that the base URL where the application runs is `http://localhost:3000`.

```
const express = require('express');
const app = express();
const port = 3000;

app.use(express.json());

app.listen(port, () => {
  console.log(`Rock album server is running on port ${port}.`);
});
```

Install the utility package **Nodemon** , a big helper in the development process, which will speed up your work by automatically restarting the server after each change in the server code. Nodemon is installed as a development dependency, as it is only needed for development purposes.

```
npm i nodemon -D
```

In the `package.json` file, inside the `scripts` property, add a script named `serve` with `nodemon app.js` command. If you don’t see the `scripts` property, you can create one. You’ll be using this `serve` script to start the server.

```
...
"scripts": {
    "serve": "nodemon app.js"
  }
...
```

Start the server with the following command:

```
npm run serve
```

This should appear in your terminal window:

![](https://www.split.io/wp-content/uploads/2021/07/micah-01.png?width=968)

Without Nodemon, you can always run your app directly by `node app.js`.

## Add In-Memory Database in Node

In this tutorial, you won’t set up a proper database server that will store the data permanently. Instead, you’ll use an in-memory database that can be wired up in a few minutes. First, install a simple and universal database layer provided by the `@nano-sql/core` npm package:

```
npm i @nano-sql/core
```

Among many other things, this [library](https://www.npmjs.com/package/@nano-sql/core) provides a simple API to quickly set an in-memory database. You’ll want to create that database as soon as the server application runs, but you’ll also want to isolate the logic for creating the database in a separate file – a service – called `databaseService.js`. Put the following code inside the file:

```
const nSQL = require("@nano-sql/core").nSQL;

const createDatabase = () => {
  nSQL().createDatabase({
    id: "albums-db",
    mode: "TEMP",
    tables: [
      {
        name: "albums",
        model: {
          "id:int": { pk: true, ai: true },
          "name:string": { notNull: true },
          "artist:string": { notNull: true },
          "rating:float": {}
        }
      }
    ]
  }).then(() => {
    return nSQL("albums").query("upsert", [
      {
        name: "Dark Side of the Moon",
        artist: "Pink Floyd",
        rating: 4.7,
      },
      {
        name: "Back In Chartreuse",
        artist: "AC/DC",
        rating: 4.3,
      }
    ]).exec();
  });
};

module.exports = {
  createDatabase
};
```

So, what’s going on here? The service is exposing only one method – the one for creating the database. `@nano-sql/core` library is exposing the `nSQL` method, which can be used to create a database. The `mode: "TEMP"` part of the configuration object instructs `@nano-sql/core` to create an in-memory database (alternatively, you’ll want to set the value of `PERM` instead of `TEMP` to switch to a persistent storage mode). Configuration object defines the one table this app needs – `albums`. The table is provided with a data model, where `id` is marked as both primary key and autoincrement: `"id:int": { pk: true, ai: true }`.

There are three columns defined in the `albums` table: `name` and `artist` are defined as mandatory (`{notNull: true}`), while `rating` column will have an optional value, and that is by default in `@nano-sql/core`. `createDatabase` method is implemented to instantly seed the `albums` table with two records, which is performed after the promise from successfully creating the database returns (the `then()` part of the code).

The database with seeded data will be created if you call the `createDatabase` method imported from `databaseService` inside `app.js`. Add these two lines to `app.js`:

```
...
const databaseService = require('./databaseService');
databaseService.createDatabase();
```

## Create Five Endpoints, a Controller, and a Service

The server application you’re creating still doesn’t do much, as there is currently no way clients can communicate with the application. That communication is done via endpoints (predefined routes) you’ll configure shortly. You’ll want to set up five “classic” CRUD endpoints for the album entity:

- `GET /albums` – get all albums
- `GET /albums/:id` – get a single album by id
- `POST /albums` – create a new album
- `PUT /albums/:id` – update existing album
- `DELETE /albums/:id` – delete existing album

But creating endpoints is just the tip of the server iceberg. Your application still needs working parts that will listen for client requests and provide meaningful responses to those same clients, based on request data. Those parts are called _controllers_. Good development practice suggests controllers should be trimmed of as much business logic as possible – as that logic should live in another essential part of a well-designed server app – a _service_.

In this section, you’ll emulate that architecture in practice. Start with making a service file called `albumService.js`, that will have the business logic of communication with the database.

```
const nSQL = require("@nano-sql/core").nSQL;

const getAllAlbums = async () => {
  const result = await nSQL("albums").query("select", ["id", "name", "artist"]).exec();
  return result;
};

const getAlbumById = async (id) => {
  const result = await nSQL("albums").query("select", ["id", "name", "artist"]).where(["id", "=", id]).exec();
  return result;
};

const addAlbum = async (payload) => {
  const result = await nSQL("albums").query("upsert", payload).exec();
  return result;
};

const updateAlbum = async (payload) => {
  const result = await nSQL("albums").query("upsert", payload).exec();
  return result;
};

const deleteAlbum = async (id) => {
  const result = await nSQL("albums").query("delete").where(["id", "=", id]).exec();
  return result;
};

module.exports = {
  getAllAlbums,
  getAlbumById,
  addAlbum,
  updateAlbum,
  deleteAlbum
};
```

As you can probably guess these five methods are aligned with the five endpoints from the top of this section. Bear in mind that queries inside methods `getAllAlbums` and `getAlbumById` are explicitly set to return only the `name` and the `artist` field for each album record, while the `rating` field will not be returned just yet. Adding the `rating` field to the response will be a new feature that you’ll add in one of the following sections while learning all about feature flags. For now, proceed to make a controller file called `albumController.js`, which will use `albumService` as a dependency, and heavily lean on the implementation within.

```
const albumService = require('./albumService');

const getAllAlbums = async (request, response) => {
  const result = await albumService.getAllAlbums();
  response.status(200).json(result);
};

const getAlbumById = async (request, response) => {
  const id = parseInt(request.params.id);
  const result = await albumService.getAlbumById(id);
  response.status(200).json(result);
};

const addAlbum = async (request, response) => {
  const result = await albumService.addAlbum(request.body);
  response.status(200).json(result);
};

const updateAlbum = async (request, response) => {
  const body = {
    ...request.body,
    id: parseInt(request.params.id)
  };
  const result = await albumService.updateAlbum(body);
  response.status(200).json(result);
};

const deleteAlbum = async (request, response) => {
  const id = parseInt(request.params.id);
  const result = await albumService.deleteAlbum(id);
  response.status(200).json(result);
};

module.exports = {
  getAllAlbums,
  getAlbumById,
  addAlbum,
  updateAlbum,
  deleteAlbum
};
```

Notice how the controller above is only worrying about properly handling the `request` and `response` objects. Controller methods extract required data from the requests (if needed), and handle the responses that should be sent back to the client. But everything in the middle – in this case, database communication logic – is delegated to the service.

You’re probably wondering if all this separation is a bit redundant, given the fact that methods isolated in the `albumsService` consist of mostly one-liner logic – and it is a perfectly valid point. But in real-world apps, the business logic hidden behind endpoints is rarely that simple, and controllers would become overcrowded with code in no time. The other benefit of this approach is that methods exported from the service are isolated and can be reused, so it’s recommended to get a clean “architectural” start, no matter how simple your application is at the very beginning of the development.

Finally, in `app.js`, import `albumController` and add route handlers at the bottom (after the `databaseService.createDatabase()` call):

```
...
const albumController = require('./albumController');

app.get('/albums/', albumController.getAllAlbums);
app.get('/albums/:id', albumController.getAlbumById);
app.post('/albums/', albumController.addAlbum);
app.put('/albums/:id', albumController.updateAlbum);
app.delete('/albums/:id', albumController.deleteAlbum);
```

The added code creates five endpoints or middlewares as a part of the Express server. If a request comes to the server, its URL and HTTP method are matched against these configured endpoints. When a certain route configuration is matched with the request, a given method inside `albumController` will run as a middleware.

After putting all of this together, you’ll probably want to be sure that the server actually works. That can be easily checked with **HTTPie**. Use the following command to get all albums from the database:

```
http :3000/albums 
```

That should give you back the two seeded album entries, sans the rating field, as previously defined:

```
[
    {
        "artist": "Pink Floyd",
        "id": 1,
        "name": "Dark Side of the Moon"
    },
    {
        "artist": "AC/DC",
        "id": 2,
        "name": "Back In Chartreuse"
    }
]
```

Test if writing to the database works by posting a new record:

```
echo '{"name":"Slippery When Wet","artist":"Bon Jovi"}' | \
http :3000/albums
```

Finally, make sure to update the fundamentally wrong color choice for the AC/DC album, by executing a `PUT` request:

```
echo '{"name":"Back In Black","artist":"AC/DC"}' | \
http PUT :3000/albums/2
```

## Creating a Feature Flag

Feature flags represent a widely used practice in the software development world. Almost every application gets released in cycles, with each cycle bringing a new set of features to the user audience. Sometimes, those features are not intended to be delivered to the whole pool of application users, as application owners might want to perform A/B testing. On other occasions, the features are simply not tested enough and they should be available only to the testing audience. Feature flagging is a useful tool in all those situations, ensuring that a given set of features gets delivered only to a targeted group of users. If you’ll be using feature flags inside your application, it’s always a good idea to introduce them early, as that way feature flag branching logic can naturally scale up together with your project.

Let’s revisit the album ratings which are currently not sent from the server to the client(s). This is not a real-world app, but it’s not hard to imagine that adding ratings to the shared album information could be a sensitive product decision. So you’d want to give this feature only to a smaller part of the user base first, as you’re not sure how this new feature will resonate with the existing user base. Imagine it like a version of A/B testing where only 25% of application users will be shown the album ratings, by a random choice. How does one achieve this? With feature flags, of course.

To create a feature flag, you’ll need access to the [Split](https://app.split.io/) application. If you don’t have a Split account yet, you should register for one [here](https://www.split.io/signup/) to continue. After you log in to Split, go to the **Splits** section on the left, and click **Create Split**. The dialog will ask you for the name of the split, which you can define as `album_split`. Leave the default settings for everything else and click **Create** to move forward.

![](https://www.split.io/wp-content/uploads/2021/07/micah-02-1024x824.png?width=1024)

This is what you’ll see next:

![](https://www.split.io/wp-content/uploads/2021/07/micah-03.png?width=924)

To add the split settings, click **Add Rules**.

_Treatment_ is the term used for the state of a feature in Split. Every feature you’d want to release through Split has at least two treatments. The most common values for treatments are `on` or `off`, but you can use custom values as well. As configured here, when the treatment is `on`, users will get an extra `rating` field as part of the response from the album `GET` endpoints. For the `off` treatment, they will get just the name and the artist for each album.

![](https://www.split.io/wp-content/uploads/2021/07/micah-04-1024x245.png?width=1024)

Set up the _targeting rules_, where the targeting audience for this split will be defined. The split will be configured as a _percentage split_, and it can be set up inside the **Set The Default Rule** section. Percentage split ensures treatments will be randomly distributed between users in the predefined ratio. As seen in the next image, you’ll define that 25% of your users will get the `on` treatment, and the remaining 75% will get the `off` treatment.

![](https://www.split.io/wp-content/uploads/2021/07/micah-05-1024x300.png?width=1024)

> Sometimes the defined split might not be wired up inside your application at runtime, so the users will branch according to what you’ve set up inside the **Set The Default Treatment** section. A good practice here is to have the `off` treatment as the default one, as you probably don’t want new features to be accessible to all users, especially if those were not tested first.

Click **Save changes** , and then **Confirm** , which will save the split settings.

## Connect Split with the Application

Back in the application, _Split Node.js SDK_ is needed to apply the previously set logic in the app’s runtime. You can install it easily, as it is made available through npm.

```
npm i @splitsoftware/splitio
```

Add this code to the top `albumController.js`:

```
const SplitFactory = require('@splitsoftware/splitio').SplitFactory;

const factory = SplitFactory({
  core: {
    authorizationKey: 'YOUR_API_KEY'
  }
});

const client = factory.client();
```

Instead of the `YOUR_API_KEY` placeholder, you should use the one from the **Admin Settings** of the Split dashboard. Click the square workspace icon in the upper left (it will usually say **DE** for a new Split account). Then click **Admin Settings** , **API keys**. The key you’ll need is the **Server-Side** from the staging environment – the second one from the list below. Please note that in a production-ready app, it would be best-practice to store this piece of configuration inside an environment variable.

![](https://www.split.io/wp-content/uploads/2021/07/micah-06-1024x429.png?width=1024)

With the SDK now wired to the app, it can be utilized to get a respective treatment for a user’s request, using the `getTreatment` method exposed on the `client` object. The method receives two parameters – _key_ and _split name_. The key is used to identify a certain user and calculate a specific treatment for that user or group of users exclusively, while the split name represents a unique identifier of the split configured in the Split dashboard.

You’ll add a custom `calculateTreatment` function in the controller that will be used to fetch the treatment for each request. It will read the user’s authentication data, e.g. the data stored in the `authorization` header, and use it as a key for the `getTreatment` method. The second argument is the name of the previously configured split (`album_split`).

```
const calculateTreatment = (request) => {
  const key = request.headers['authorization'];
  const treatment = client.getTreatment(key, 'album_split');

  return treatment;
};
```

In a living production application you’d want to have a more secure authentication mechanism to identify users, but here you can just send the unencrypted user’s data in the `authorization` header for each client request.

## Add Branching in Express Application with Feature Flags

Both `getAllAlbums` and `getAlbumById` methods inside the controller will now have different internal branching depending on the treatment value. Users that hit the `on` treatment will be forwarded to the two new functions you’ll soon add to the `albumService.js` – `getAllRatedAlbums` and `getRatedAlbumById`.

```
const getAllAlbums = async (request, response) => {
  const treatment = calculateTreatment(request);

  const result = treatment === 'on' ? await albumService.getAllRatedAlbums() : await albumService.getAllAlbums();
  response.status(200).json(result);
};

const getAlbumById = async (request, response) => {
  const id = parseInt(request.params.id);
  const treatment = calculateTreatment(request);

  const result = treatment === 'on' ? await albumService.getRatedAlbumById(id) : await albumService.getAlbumById(id);
  response.status(200).json(result);
};
```

Add rating-specific methods to the `albumService.js`. Those methods now explicitly include `rating` column inside respective database queries:

```
const getAllRatedAlbums = async () => {
  const result = await nSQL("albums").query("select", ["id", "name", "artist", "rating"]).exec();
  return result;
};

const getRatedAlbumById = async (id) => {
  const result = await nSQL("albums").query("select", ["id", "name", "artist", "rating"]).where(["id", "=", id]).exec();
  return result;
};
```

Remember to add new methods in the `module.exports` statement at the bottom of `albumService.js`, which will now look like this:

```
module.exports = {
  getAllAlbums,
  getAllRatedAlbums,
  getAlbumById,
  getRatedAlbumById,
  addAlbum,
  updateAlbum,
  deleteAlbum
};
```

If you’re still wondering what’s happening here, it will get way more clear after firing a few HTTPie requests that use an `authorization` header:

```
http :3000/albums authorization:"John"
```

> Authorization header with the value `John` obviously doesn’t make much sense in the real world. It has been used here as an example of how the feature flag key is used, and what calculations are made based on that value.

Thanks to the randomness gods, this request got either the `on` treatment or the `off` treatment, with chances leaning much more in the direction of `off` (75%). So the response you’ll see in the terminal console probably doesn’t include the `rating` field, but there’s a 25% chance it does.

A key fact to notice is that every subsequent request with the same header value (`John`), meaning the same split key and _de facto_ the same user, will give the user the same treatment as in the first try, as Split provides a [consistent user experience](https://help.split.io/hc/en-us/articles/360030024391-How-does-Split-ensure-a-consistent-user-experience-). Logic that Split executes behind the scenes ensures that for the same key parameter the `getTreatment` method returns the same treatment value every time, or at least until given different instructions in the Split configuration.

Now you want to get a different response, by hitting a different treatment? Think of a new value for the `authorization` header:

```
http :3000/albums authorization:"Paul"
```

Same as the previous one, this request also has a 25% chance of resulting in `on` treatment. If you are getting back the same response for `authorization:Paul` as for the `authorization:John`, you can keep trying with new names, or any other set of characters until you get the other treatment, resulting in shown or hidden ratings for returned albums list.

If you are unsure that your app is working properly with Split, simply change the percentage on the default rule to 100% (remember to save and confirm). If you don’t see the ratings data on the next request, something is not configured correctly in the app.

## Rethink Your Controller and Services

After successful tests, you’re probably thinking we’re done here. Indeed, there’s no more code to write, as your Node application running Express REST API with integrated feature flags obviously works. But as always in programming, there’s something to refactor. Remember good practices with controllers and services mentioned above? Well, those were not followed to the fullest where you introduced feature flags in the codebase, but that can be simply fixed.

Notice how `albumController` is now overcrowded with all things Split, while it doesn’t necessarily need to know anything about these, and can be completely decoupled from any feature flag logic. In fact, the complete code related to feature flags can be moved to its separate module – you guessed it – a service. Create a file called `treatmentService.js` and populate it with the following code, now moved away from the album controller:

```
const SplitFactory = require('@splitsoftware/splitio').SplitFactory;

const factory = SplitFactory({
  core: {
    authorizationKey: 'YOUR_API_KEY'
  }
});

const client = factory.client();

const calculateTreatment = (request) => {
  const key = request.headers['authorization'];
  const treatment = client.getTreatment(key, 'album_split');

  return treatment;
};

module.exports = {
  calculateTreatment
};
```

Besides the benefits of “cleansing” the controller, the `calculateTreatment` method now gets exposed in a standalone service to all the other potential future consumers of it. And it’s reasonable to expect such consumers will show up as your application grows: you’ll maybe start having other entities in the database soon, together with their own dedicated controllers – songs, artists, labels, music stores, etc. – and they could all use a treatment calculation logic that will always be the same.

There are a few slight adjustments needed in the `albumController.js` now. First, you need to delete all the code that now lives in the new treatment service. Additionally, at the top you need to import a reference to the `treatmentService`:

```
const treatmentService = require('./treatmentService');
```

Finally, use the exposed method from the service where needed:

```
const getAllAlbums = async (request, response) => {
  const treatment = treatmentService.calculateTreatment(request);

  const result = treatment === 'on' ? await albumService.getAllRatedAlbums() : await albumService.getAllAlbums();
  response.status(200).json(result);
};

const getAlbumById = async (request, response) => {
  const id = parseInt(request.params.id);
  const treatment = treatmentService.calculateTreatment(request);

  const result = treatment === 'on' ? await albumService.getRatedAlbumById(id) : await albumService.getAlbumById(id);
  response.status(200).json(result);
};
```

After this refactoring is performed, code inside the album controller is again aligned with the initial idea – there is no tight coupling between the controller and the split logic. Album controller is now using two different dependencies to perform what’s needed – `albumService` and `treatmentService` – but both now “live” away from the controller. Those services expose methods that are used by the controller but can also be (re)used by any other file down the road. Also, the whole split logic can be removed from the controller if needed, with only slight adjustments in the code.

## Level Up Your Refactor Game

Your codebase is in a good place now, but the catch with the refactor is that it can always get better. Rewrite `albumController` not to depend on `treatmentService` anymore. All the split logic will now take place in the `albumService`. This way, the controller is completely trimmed of any split-related stuff. Remove the `treatmentService` import statement, and rewrite the two methods in the controller like this:

```
const getAllAlbums = async (request, response) => {
  const key = request.headers['authorization'];

  const result = await albumService.getAllAlbums(key);
  response.status(200).json(result);
};

const getAlbumById = async (request, response) => {
  const id = parseInt(request.params.id);
  const key = request.headers['authorization'];

  const result = await albumService.getAlbumById(key, id);
  response.status(200).json(result);
};
```

The key is now extracted from the request headers at the controller level, and there is no passing around of the `request` object anymore. `key` is passed down to the methods of the `albumService`, which needs to be adjusted:

```
const treatmentService = require('./treatmentService');

const getColumns = (key) => {
  let columns = ["id", "name", "artist"];
  const treatment = treatmentService.calculateTreatment(key);

  if (treatment === 'on') {
      columns.push('rating');
  }

  return columns;
}

const getAllAlbums = async (key) => {
  const result = await nSQL("albums").query("select", getColumns(key)).exec();
  return result;
};

const getAlbumById = async (key, id) => {
  const result = await nSQL("albums").query("select", getColumns(key)).where(["id", "=", id]).exec();
  return result;
};
```

`treatmentService` is now a dependency of another service – `albumService`, which is a perfectly valid setup, so the `require` statement needs to be added at the top. Additionally, treatment calculation is now encapsulated inside a single method – `getColumns` – so it’s not repeated inside the `GET` methods. This allows you to remove the treatment-specific methods `getAllRatedAlbums` and `getRatedAlbumById`, as the columns that need to be returned are now calculated on the fly. While this may seem like a small refinement, the benefits to future code updates add up quickly:

The controller is now “clean”. There are no external concerns (such as Split)  
The `albumsService` has now isolated all the interactions with Split into a single function that can be easily removed once the feature flag is rolled out to 100% of users  
The `albumsService` no longer needs specialized functions to distinguish between getting all albums with and without ratings

## Final Thoughts

Hopefully, this was a vivid example that explained the advantages of using best practices with controllers and services in a Node application. In a nutshell, controllers are handling client requests and delegate heavy-lifting work to services based on the request data. You’ll want to remember a few key benefits of using a service:

- controllers get smaller and more readable
- methods in services can be reused in a much simple way
- it is easier to add new features and code refactoring
- it is easier to perform tests

And speaking of best practices, there’s no better one than using feature flags in a production application. Branching with feature flags drastically improves user experience across your application, eventually leading to a better product and more satisfied customers. Feature flags can be seamlessly integrated into the [software delivery lifecycle](https://www.split.io/blog/continuous-deployment-feature-flags/) and popular technical stacks for server-side applications, like the ones running on Node and Express.