loading...
Cover image for NodeJs + Express vs NestJs, a vision about architecture and good pratices

NodeJs + Express vs NestJs, a vision about architecture and good pratices

giovannikleincampigoto profile image Giovanni Klein Campigoto ・4 min read

The problem

Bootstrapping an app using node has been often easy, because of the low complexity of creating a node backend, a little bit of express configuration and you're done. Soon enough, you will find yourself asking the following questions:

  • Do I need to follow any kind of pattern?
  • Worry about variable types?
  • What kind of folder structure to use?

The good thing about Node is that it makes you feel free when you have to take a decision about architecture, variables or folder structure. It is easy to start a writing an application, but hard to maintain it and be congruent about data typing.

The Possible Solution

Use some kind of Javascript framework, there are many options out there, but for this post we are using NestJs.


NestJs logo


PROS:
  • Uses the good old MVC pattern;
  • Has a CLI to generate code for you;
  • Has typechecking, avoiding variable typing bugs.
CONS:
  • Has a learning curve, specially if you don't know Typescript very well and don't use the MVC pattern very often;
  • Could be not flexible.
  • A lot of stuff is encapsulated by Nest (e.g express).

A basic Node + Express App

Just from looking the at snippet bellow you can see that creating a Node + Express App is pretty simple.

// index.js
const express = require('express');
const app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

Then in your terminal:

foo@bar:~$ node src/index.js
Example app listening on port 3000!

Right now your folder structure should look like this:

.
├── node_modules
├── src
|   └── index.jsvar

Done! If you acess http://localhost:3000 and you will get a Hello World! from the browser.

Pretty simple right?

Now let's say that you have some new routes:

// index.js
const responses = require('../responses');
const {getUser} = require('../services/users');
const express = require('express');
const app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.get('/users', function (req, res) {
  const { userId } = req.query;
  res.send(getUser(userId));
});

app.post('/login', function (req, res) {
  // ... do some kind of authentication ...
  res.send(responses.login);
});

app.post('/resetPassword', function (req, res) {
  res.send(responses.resetPassword);
});

.
.
.

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

Soon enough your routes will begin to bloat, you'll have to use the router from express.

// UserServices.js

function getUser (userId) {
  // ...do some request to the database...
  return {
    name: 'John Doe',
    age: 21
  };
}

module.exports = {getUser}

Suddenly you're with this folder structure:

.
├── node_modules
├── src
|   |── UserServices.js
|   |── LoginServices.js
|   └── index.js

The code above is messy and the folder structure is not optimal as well. It's easy to understand that the new files belong to a folder called services.

So you change your folder structure to this:

├── node_modules
├── src
|   |── services
|   |   |── UserServices.js
|   |   └── LoginServices.js
|   |── routes
|   |   └── UserRoutes.js
|   └── index.js

Now you have a certain separation of concerns, but you can return literally anything from your getUser and login functions, it's easy to someone do some mistake and send something that will break your API (for e.g name: null).

How could you solve this?

Let's create a model:

//UserModel.js
const user = (name, age) => {
  return {
    name: typeof name === string ? name : null,
    age: typeof age === number ? age : null
  }
}

It should go under the models directory:

├── node_modules
├── src
|   |── services
|   |   |── UserServices.js
|   |   └── LoginServices.js
|   |── routes
|   |   └── UserRoutes.js
|   |── models
|   |   └── UserModel.js
|   └── index.js

But that's pretty weird right? It should be simpler, type checking with plain Javascript is often a pain...

As your app grows, things will get complicated and you'll regret not following good pratices about backend architecture or type checking.

Enter NestJs

To create a new app:

foo@bar:~$ npm i -g @nestjs/cli
foo@bar:~$ nest new project-name

Nest will create this folder structure for you:

├── node_modules
├── src
|   |── app.controler.ts
|   |── app.service.ts
|   |── app.module.ts
|   └── main.ts
├── nest-cli.json
├── package.json
├── package-lock.json
├── tsconfig.build.json
├── tsconfig.json
└── tslint.json

Main server file will be:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

In your terminal, for runing the dev environment:

foo@bar:~$ npm run start:dev

Same thing with express, but it's already configured for you, you'll get a Hello World! at http://localhost:3000.

Routing is made by app.module.ts, which have the following syntax:

// app.module.ts
import { Module } from '@nestjs/common';
import UserController from 'controllers/user'
import UserService from 'services/user'

@Module({
  imports: [],    
  controllers: [UserController],
  providers: [UserService],
})
export class AppModule {}

For creating a model, type checking will throw an error if you try to create a user with a property that is not in the interface or doesn't match the typing criteria:

export default interface UserModel {
  name: string,
  age: number,
  height?: number
}

That's way better for controlling your data flow, but now you have to write types for everything. That's good, because you don't want to mess up your user's data!

Conclusion

This architecture helps your written code to be much cleaner, folder structure will follow a pattern everytime you start a new app and best pratices will be at your hands.

There is a lot more to cover, but I will not cover in this article as it is very long already. Here is a potato for compesation:

The potato

Posted on by:

giovannikleincampigoto profile

Giovanni Klein Campigoto

@giovannikleincampigoto

I am Frontend Engineer at Junto Seguros, we work with React, Gatsby, Next and all the tasty stuff of Frontend development.

Discussion

markdown guide