DEV Community

Daniel Schmitz
Daniel Schmitz

Posted on

Creating Node Applications using SOLID principles

It's easy to create a Node application (using for example the Express framework). The following example is generated by express-generator and works perfectly:

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

The problem with an application built this way is maintenance. Adding new features or fixing a bug may seem easy when we are in the project development phase. After several months without seeing the project, a simple addition of functionality can take several hours.

Design patterns were created to solve this problem: Maintenance! Don't think that a pattern will make you write less code, we always write more code or create more files.

Among the hundreds of existing standards, we have one in particular called SOLID, which is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible and maintainable.

Among these 5 principles, we will use the first two to write a Node application in a more organized way.

The first is called Single Responsibility Principle (the letter S) and has the following concept: A class should have one, and only one, reason to change. That is, a class or file should do only one thing, and have only one responsibility. A class/file that performs several distinct tasks should be avoided as much as possible.

The first example code displayed at the beginning of this article does not follow the SRP (Single Responsibility Principle) rule since the code performs several distinct functions.

The second principle is called Open Closed Principle (the letter O) and has the following concept: A class or file should be open for extension but closed for modification. In other words, when writing a new feature in the application, the ideal is to create a new file or class instead of editing the existing code.

Using just these two rules, we will create a Node application using the Express framework, in a structured way.

Project creation

With Node 8 or higher installed, run the following command on your favorite terminal.

$ mkdir solid-express-app
$ cd solid-express-app
$ npm init -y

The npm init -y command will create the package.json file with some initial settings:

Install the Express framework with the following command:

$ npm i -s express
+ express@4.17.1added 50 packages
found 0 vulnerabilities

The npm i (acronym for npm install) command installs a node package, and the -s attribute will record the package in the package.json file. The package.json file now has the following structure:

Create the index.js file and use your favorite text editor to open the solid-express-app folder. In this article we will use the Visual Studio Code.

$ touch index.js
$ code .

Initially create the following code:

const express = require('express')
const app = express();

app.get('/', function(req,res) {
    res.send('Hello Wolrd dev.to')
})

app.listen(3000, function () {
    console.log('Server running...')
})

To run this code, we will use the nodemon package which has the feature to restart the node service whenever the file changes.

$ npm i -D nodemon

We installed the nodemon with the -D option, which will save this package to the devDependencies of package.json. If you don't know this option or aren't familiar with Node, see more information at this link.

Edit the package.json file and add the start script, according to the code below:

{
  "name": "solid-express-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.2"
  }
}

To run the start script, execute the following command:

With the server running, open in the browser the following address: http://localhost:3000

Reviewing the project

The application created so far has only the index.js file, and this contains two distinct features. The first uses the command app.get to create a route, answering with the message "Hello World dev.to". The second uses the app.listen command to start the web server.

To use the SOLID pattern, we have to isolate each functionality in files, and make the index.js file run these features.

First, it is necessary to separate the functionalities of the application. If you have no experience with Node development, the important thing is to understand how it will be created. We will add each feature in a folder called "libs".

const express = require('express')
const app = express()

const libs = require('./libs')
libs.forEach(lib => require(`./libs/${lib}`)(app))

The require('./libs') code will fetch from the file ./libs/index.js, and on the next line we use the forEach command to run each file, passing the app constant.

Create the libs directory, and add the index.js file with the following code:

module.exports = [
    'helloWorld',
    'start'
]

Add the file libs/helloWorld.js, with the following code:

module.exports = app => {
    app.get('/', function(req,res) {
        res.send('Hello Wolrd dev.to')
    })
}

Add the file libs/start.js, with the following code:

module.exports = app => {
    app.listen(3000, function () {
        console.log('Server running...')
    })
}

So far, we have the following files on the project:

The result of this refactoring is the same for the user. In the browser, there is no change. Now, we are respecting the Single-responsibility principle because each functionality is properly separated into files, and the Open-closed principle because when creating a new functionality, we must create a new file.

Adding new features

For example, if we want to add a new feature, like the cors module, we should do the following:

$ npm i -s cors

Change the libs/index.js file:

module.exports = [
    'cors',
    'helloWorld',
    'start'
]

Add the libs/cors.js file:

const cors = require('cors')

module.exports = app => {
    app.use(cors({
        exposedHeaders: 'Authorization'
    }))
}

When adding the cors module, see that we created a new http header in the request called `exposedHeaders'. To check if this change worked, use the Google Chrome Dev Tools (F12 key) and on the network tab search for "Access-Control-Expose-Headers".

Conclusion

The use of design patterns can slow down the pace at which you write software a little. Whenever you create a new feature you should create a new file, this can be boring or tedious at first, but it will help you maintain the software over time.

For example, if from this moment on you assume that all software will follow the SOLID standard, you will know in the future that no matter what project you are carrying out maintenance, when creating a new feature you should create a new file or class.

To access a more complex project, check this github project

If you found an error in my English, please let me know at danieljfa at gmail dot com

Top comments (0)