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
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
Dependencies
npm i express dotenv
• 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
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;
Where Controller interface will be like this:
import { Router } from 'express';
interface Controller {
path : string;
router : Router;
}
export default Controller;
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;
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;
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();
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)
Nice.
What does
'@/controllers/ControllerFile'
do please? How does one get it? It doesn't work over hereSorry 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
after editting
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: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