DEV Community

Cover image for Building Scalable and Maintainable Node.js Applications with TypeScript and OOP Principles
Omar3ain
Omar3ain

Posted on

Building Scalable and Maintainable Node.js Applications with TypeScript and OOP Principles

TypeScript is a powerful language that adds a layer of type safety and code organization to JavaScript. It has become increasingly popular in the Node.js community due to its ability to provide better code quality, maintainability, and scalability. In this article, we will explore how to use TypeScript with Node.js using Object-Oriented Programming (OOP) principles.

Object-Oriented Programming is a programming paradigm that focuses on creating objects that encapsulate data and behavior. In TypeScript, we can create classes that define the structure and behavior of objects. Classes can have properties, methods, and constructors, and can inherit from other classes.

Let's build an application

1. First we need to make a new folder and initialize npm in this folder by the following commands:

mkdir myproject
cd myproject
npm init -y
Enter fullscreen mode Exit fullscreen mode

2. To use TypeScript with Node.js, we first need to install the TypeScript compiler and other dependencies. We can do this using the npm package manager by running the following command:

Development dependencies

npm i -D typescript 
tsc-watch
eslint 
prettier 
eslint-config-prettier
eslint-plugin-prettier
@typescript-eslint/parser 
@typescript-eslint/eslint-plugin
@types/node @types/express

Enter fullscreen mode Exit fullscreen mode

Dependencies

npm i express dotenv
Enter fullscreen mode Exit fullscreen mode

typescript is the package that adds TypeScript support to the project.

tsc-watch is a package that allows you to watch for changes to TypeScript files and automatically recompile them.

eslint is a package that provides linting functionality for the project.

prettier is a package that provides code formatting functionality for the project.

eslint-config-prettier and eslint-plugin-prettier are packages that configure ESLint to work with Prettier.

@typescript-eslint/parser and @typescript-eslint/eslint-plugin are packages that provide ESLint rules specific to TypeScript.

@types/node and @types/express are packages that provide TypeScript definitions for the Node.js and Express libraries.

express is a popular Node.js web framework that simplifies the process of building web applications. It provides a set of features for handling HTTP requests and responses, routing, middleware, and more. By using express, developers can create scalable and maintainable web applications with ease.

dotenv is a package that allows you to load environment variables from a .env file into your Node.js application. This is useful for storing sensitive information such as API keys, database credentials, and other configuration settings. By using dotenv, developers can keep their application secrets secure and separate from the application code.

By installing these packages, developers can benefit from TypeScript's type-checking, linting, and code formatting features, which can help catch errors early and make the codebase more maintainable.


3. So you can make the structure of the project like this or you can structure it as you want:

├── node_modules/ 
├── src/
│   ├── interfaces/
│   ├── controllers/
│   ├── middleware/
│   ├── models/
│   ├── routes/
│   ├── app.ts        --------> Demo of this file will be in the 
│   │                           next point same for server.ts
│   └── server.ts     
├── tests/         
│   ├── unit/
│   └── integration/
├── .env             -------->  contains environment 
│                               variables used by the 
│                               application, loaded in through 
│                               the dotenv package.
│
├── .eslintrc.json   --------> .eslintrc.json and .prettierrc
│                              contain configuration for ESLint 
│                              and Prettier, respectively, which 
│                              are used for  code linting and 
│                              formatting.
│                             
├── .prettierrc
├── package.json
├── tsconfig.json     --------> contains the configuration for 
│                              TypeScript, which includes 
│                             information on how to transpile the 
│                             TypeScript code into JavaScript
│                             that can be run by Node.js.
└── README.md
Enter fullscreen mode Exit fullscreen mode

4. App.ts file will be like this:

import express , { Application } from 'express';
import mongoose from 'mongoose';
import Controller from '@/utils/interfaces/controller.interface';
import ErrorMiddleware from '@/middlewares/error.middleware';


class App {
  public expess: Application;
  public port :number;

  constructor(controllers : Controller[] , port : number) {
    this.expess = express();
    this.port = port;
    // THE ORDER OF THESE FUNCTIONS IS IMPORTANT 
    this.initializeDatabaseConnection();
    this.initializeMiddleware();
    this.initializeControllers(controllers);
    this.initializeErrorHandling();
  }

 private initializeDatabaseConnection() : void {
    const { MONGO_USERNAME, MONGO_PASSWORD, MONGO_PATH } = process.env;
mongoose.connect(`mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}${MONGO_PATH}`);
  }

  private initializeMiddleware() : void {
    this.expess.use(express.json());
    this.expess.use(express.urlencoded({ extended: true }));
  }

  private initializeControllers(controllers : Controller[]) : void {
    controllers.forEach((controller : Controller) => {
      this.expess.use('/api', controller.router);
    })
  }

  private initializeErrorHandling() : void {
    this.expess.use(ErrorMiddleware);
  }

  public listen() : void {
    this.expess.listen(this.port, () => {
      console.log(`App listening on port ${this.port}`);
    })
  }

}

export default App;
Enter fullscreen mode Exit fullscreen mode

Where Controller interface will be like this:

import { Router } from 'express';

interface Controller {
  path : string;
  router : Router;
}

export default Controller;
Enter fullscreen mode Exit fullscreen mode

and ErrorMiddleware will be like this:

import { Request , Response , NextFunction } from "express";

function ErrorMiddleware(error: Error  ,req: Request, res: Response, next: NextFunction) : void {
  const status = error.status || 500;
  const message = error.message || "Something went wrong";

  res.status(status).send({
    status,
    message
  })

}
export default ErrorMiddleware;
Enter fullscreen mode Exit fullscreen mode

5. Every controller will implement Controller interface like this :

import { Router, Request, Response, NextFunction } from 'express';
import Controller from '@/interfaces/controller.interface';

class ControllerExample implements Controller {
    public path = '/posts';
    public router = Router();

    constructor() {
        this.initializeRoutes();
    }

    private initializeRoutes(): void {
        this.router.post(
            `${this.path}`,
            this.HttpHandle
        );
    }

    private HttpHandle =(
        req: Request,
        res: Response,
        next: NextFunction
    ): Promise<Response | void> => {
       return res.json({message : "success"})
    };
}

export default ControllerExample;
Enter fullscreen mode Exit fullscreen mode

6. Finally server.ts file will be like this:

import 'dotenv/config';
import App from './app';
import ControllerExample from '@/controllers/ControllerFile';


const app = new App(
    [new ControllerExample()],
    Number(process.env.PORT)
);

app.listen();
Enter fullscreen mode Exit fullscreen mode

The first line imports and loads the environment variables specified in a .env file using the dotenv package.

An instance of the App class is created with an array of controllers and it has one controller for now (ControllerExample) and a port number specified in the environment variables.

Finally, the listen() method of the App instance is called to start the server and listen for incoming requests.


This code demonstrates a clean and organized way to start a server for a Node.js application built with TypeScript, with controllers separated by resources and environment variables defined in a .env file.


Thank you for taking the time to read my article and hope all of you learn something from it.

Follow me on LinkedIn : Click here

Top comments (2)

Collapse
 
orimdominic profile image
Orim Dominic Adah

Nice.
What does '@/controllers/ControllerFile' do please? How does one get it? It doesn't work over here

Collapse
 
omar3ain profile image
Omar3ain • Edited

Sorry for that i should have mentioned it in the article so to do that you need to make module aliases in package.json and tsconfig.json files so that you remove the headache of paths between TypeScript and JavaScript files and to do that

1.First in ts tsconfig.json
before editing it will be
Image description

after editting

Image description

now you can use @/* in typescript files but in javascript files it does not know those kind of paths YET

2.So you need to make javascript files know what @/* means , first there is a package you need to install npm i module-alias then package.json file you will add new property "_moduleAliases" and set it to an object like this:

Image description

3.Finally in index.ts file you need to import this import 'module-alias/register';

so now you made path aliases for all the project files