Categories
Node.js Best Practices

Node.js Best Practices — Project Structure

Spread the love

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices we should follow when writing Node apps.

Folder Structure

Our Node app should follow some standard folder structure.

For example, we can have something like:

src
│   app.js
└───api
└───config
└───jobs
└───loaders
└───models
└───services
└───subscribers
└───types

app.js is the app’s entry point.

api has the controllers for the endpoints.

config has the environment variables and configuration related stuff.

jobs have scheduled jobs.

loaders have the code that runs when the app starts.

models have the database models.

services has business logic.

subscribers have the event handlers for queues, etc.

types are type definitions for TypeScript projects.

3 Layer Architecture

The 3 layer architecture consists of the controllers, service layer, and the data access layer.

The controller is the interface to the client.

It takes requests and sends responses.

The service layer has the logic, which takes stuff from controllers and returns things to them.

The data access layer has the database logic which talks to the service layer.

We should never put business logic in controllers.

Separating them makes testing them easier when we write unit tests.

When we test controllers, we can just mock all the service entities.

Service Layer for Business Logic

The service layer is used for business logic.

It’s all about doing everything that controllers and database layers don’t do.

For example, we can write:

route.post('/',
  validators.userSignup,
  async (req, res, next) => {
    const userParams = req.body;
    const { user } = await UserService.Signup(userParams);
    return res.json(user);
  });

The UserService.Signup has the business logic.

The request and response are handled by the controller.

Pub/Sub Layer

The pub/sub layer is used for listening to events from external sources.

The pub part sends data to other modules.

Separating this logic makes sense since they create a cohesive layer.

Dependency Injection

Dependency injection lets us handle all the dependency initialization in one place.

For example, we can write:

export default class UserService {
  constructor(userModel, companyModel, employeeModel){
    this.userModel = userModel;
    this.companyModel = companyModel;
    this.employeeModel = employeeModel;
  }

  getUser(userId){
    const user = this.userModel.findById(userId);
    return user;
  }
}

We have the UserService class that has takes all the required dependencies in the constructor.

Then we use throughout the class.

We can use 3rd party solutions to make this easier.

The typedi library provides us with a dependency injection container to let us inject dependencies.

We can use dependency injection with Express by writing something like:

route.post('/',
  async (req, res, next) => {
    const userParams = req.body;
    const userServiceInstance = Container.get(UserService);
    const { user} = userServiceInstance.Signup(userParams);
    return res.json(user);
  });

We call the Container.get method from typedi to get the UserService so we can use it.

Cron Jobs and Recurring Task

Cron jobs and scheduled tasks should be in their own folder.

We can use the business logic from the service layer.

Also, we shouldn’t use setTimeout or another primitive way to delay the execution of code.

Instead, we should use a library to help us with this.

This way, we can control failed jobs and have feedback on ones that succeed.

Conclusion

We should create apps with a standard structure.

The folders cohesively organize different parts of our app.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *