Introduction
Abstraction is a core objective of programming language frameworks, including LaravelPHP, NestJS, and ASP.NET. These frameworks implement the MVC (Model-View-Controller) pattern, effectively abstracting HTTP handling, routing, and database processes.
For a hands-on example of creating controllers and routers with vanilla PHP, check out this series: PHP Vanilla Blog.
In this article, we'll explore how to achieve similar functionality with NodeJS and ExpressJS. Additionally, we'll leverage TypeScript decorators extensively to build robust controllers.
NodeJS / ExpressJS Controllers Pattern API
Setup
Let's create a dummy API to implement this pattern. First, create a folder and initialize npm:
Create package.json
file with the following content:
Now install the dependencies:
Create tsconfig.json
file:
Folder Structure
Inside the root directory, we will have the following files and directories:
ENV
Create a .env
file to include the port and a dummy auth key:
API Config
Create an api.config.ts
file:
ExpressJS APP
In app.ts
, handle the ExpressJS app and the middlewares, including the controllers scanner.
The key component in this file is the ControllerScanner
, which we'll detail later.
Server
In server.ts
, the goal is to launch the ExpressJS app:
Auth Middleware
This middleware serves as a dummy implementation to showcase middleware usage in controllers:
Controllers
Controller Scanner
This file scans the controllers directory and auto-detects controller classes:
Controller Decorator
This file contains the necessary decorators and the base controller class.
Decorators: We will have three types of decorators:
-
HTTP Verbs decorators
-
Auth Guard Decorator
-
Controller Decorator
Base controller class: This class will be inherited by the controller classes and creates ExpressJS route handlers for each controller:
Inside controller.decorator.ts
, write the following code:
The Controller decorator handles the data of the route, like prefix and version (to get this for example: /api/v1
). This controller is simple and doesn't do much
I used this controllers in another project, where I implemented ZOD and Swagger. And used this decorator to generate the Swagger controller summary and tags. Also implemented the ZOD schema parser in each HTTP Verb to fetch the Request Object for Swagger metadata.
Metadata Service
This service allows us to get/set metadata:
Creating Controllers
Create two sample controllers:
Note: If a property isn't used in the scope, you can replace it with
_
. Typescript won't complain if you do that.
Index Controller
This controller defines a basic route:
You can remove the @AuthGuard
decorator and add the middleware in @Get
decorator as well. Like this:
Customers Controller
This controller defines routes to manage customers:
Conclusion
This pattern improves the structure of your NodeJS and ExpressJS projects by organizing code into distinct layers and promoting reusability. While the decorators and the controller base class add some complexity, they provide a robust way to manage routes and middleware.
Benefits:
-
Organization: Clear separation of concerns.
-
Reusability: Middleware and handlers can be reused.
-
Scalability: Easier to add new features.
This article aimed to explore the implementation of design patterns and industry standards. While the provided code may not be optimal for production use, it serves as a valuable exercise in understanding the inner workings of frameworks and their abstractions.
For a more comprehensive and use-ready API, which includes enhanced features with ZOD and Swagger, check out the project on GitHub.
You will find also this code in this GitHub Repository