ExpressJS routing with Decorators, Dependency Injection and reflect-metadata

Vikas Kesakar
Globant
Published in
5 min readJan 21, 2022

TypeScript is a strongly typed programming language which builds on JavaScript giving you better tooling at any scale — TypeScript

The goal of this article is to help to build out-of-box solutions with Node.js, TS to define routing using decorators over the traditional route registration.

This story's target is to show how to define, register and use a route decorator with an express server.

Background

TypeScript is a typed superset of JavaScript. TypeScript really excels when it comes to object-oriented programming with JavaScript. It supports OOPS pillars such as Classes, Inheritance, Polymorphism, and Encapsulation.

Dependency injection is passing dependency to other objects or frameworks (Dependency Injector).

With the introduction of TS and ES6, some additional features are required in some aspects to support annotating and modifying classes and class members. Decorators do the job for us.

Decorators are a stage 2 proposal for JavaScript and are available as an experimental feature of TypeScript. — TypeScript

Prerequisites

  1. Make sure you have installed Node.js
  2. Basic knowledge of JavaScript, TypeScript, Node.js, ExpressJS, and decorators
  3. Code editor(VSCode)

What is Dependency Injection?

Dependency Injection

Before diving deep into dependency injection, we need to understand the first IoC container.

Inversion of Control is inverting the whole program flow, so the program manages all program dependencies. This container becomes responsible for constructing every object. When a class needs an object to instantiate, the IoC container serves the required dependency.

In dependency injection, we need to understand four different types of roles:

  • Service: Services are what we expose out. These classes are instantiated and used by IoC containers.
  • Client: Uses these services through IoC container.
  • Interface: Ensures that client and service live in correspondence.
  • Injector: Provides instantiated service.

Example: This example demonstrates dependency injection using TypeDI with typescript.

Example of Dependency Injection and IoC Container

Note: To make services injectable, we are using @Service decorator from TypeDI

Introduction to reflect-metadata

Proposal to add Metadata to ECMAScript

We will be using the reflect-metadata package for metadata reflection to define, access routes associated with each controller. TypeScript has extended reflect-metadata, which allows us to get function, arguments, and return types in concurrence with emitDecoratorMetadata.

Note: Metadata will only be emitted for a service or a component if the class has a decorator on it. It doesn’t matter which decorator. Any decorator will cause metadata to be emitted.

Let’s take a look to make it work with TypeScript. We need to enable it in tsconfig.json

tsconfig.json changes to support relection
// define metadata on an object or property Reflect.defineMetadata(metadataKey, metadataValue, target); Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);// check for presence of a metadata key on the prototype chain of an object or property 
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);
// get metadata value of a metadata key on the prototype chain of an object or property
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);

Decorators

Before diving into the decorators, keep some things in mind,

Decorators are called when class is declared, so while working with decorator, we will have only class declaration and not the instantiation.

Decorators are the design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class. It’s just a wrapper around the function which is used to enhance the functionality without modifying the underlying function.

The decorators have already been used in languages like Python, C#, and now in JS and TS.

Examples: This implements Log decorator.

function Log() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("Log called")
};
}
class Logger {
@Log
info(){}
}

See here for detailed information about the decorator evaluation order.

Creating Routes with Decorators

Route Definition

It is an interface that defines the shape of our route

RouteDefinition interface

Route decorator

It will be convenient to have a decorator for every HTTP method, for simplicity, we will be seeing only @Gethere. It’s important to set target target.constructor sand not just target to properly handle metadata.

  1. Here, the target is the object that owns the method whose parameters have been decorated. In this case, it is UserController.
  2. propertyKey: It is the method name whose signature has been decorated. propertyKey in the below example is get from UserController.

We are using @Service decorator from TypeDI, to make service injectable for controller

The below example demonstrates the definition of Get decorator and a route decorated using Get.

@Get decorator

Executing /api/user should return a list of all users. But before this fully works, we need to register the routes.

Follow in the next section about controller Decorator to see route registration.

Creating Controller Decorator

Our controller will be decorated with this decorator and contain the prefix for this controller.

The example below demonstrates the definition of controller decorator where we are defining prefixes, routes with controller metadata using reflect-metadata. The routes should almost never be undefined, except our controller has no decorated method.

Controller Decorator

Defining Route using Controller and Route Decorator

Steps:

  1. Define express router
  2. Access prefix, and routes for reflection.
  3. For every route definition in routes, register path, and handler with the express router.
  4. Export router to register with an app in index/app.ts
Controller Decorator

In the above example, we can see the following things

  • Controller decorator definition
  • Reflect to save metadata of prefix and initiate routes if not exist (The routes should almost never be undefined, except our controller has no decorated method)
  • IoC container is used to get Controller instance
  • Route registration with express router
Use router

Note: Use exported router from controller decorator in index.js as app.use(router)

Conclusion

As you’ve seen it’s pretty easy to manage express routing via TypeScript Decorators. The reflect-metadata is TypeScript’s extension which helps to maintain metadata related to the decorator. So, this metadata is used to store routes as the decorator gets called when the class is declared.

The complete code can be found on CodeSandbox.

--

--