Utilizing the Composition Root concept in a TypeScript Express application

I am trying to grasp the concept of implementing a composition root in a project.

Based on my research, improper usage of the composition root (such as referencing it in multiple places within your application code) can lead to the service locator antipattern.

Allow me to present an example of a project without a composition root.

Here is the current project structure:

  • server.ts
  • domain.ts
  • application.ts
  • api.ts
  • sql-repository

server.ts:

This file imports the API and sets up the server.

import express from 'express';
import API from './api'

const app = express();
const port = 3000;

app.use(express.json());

app.use(API);

// Start server
app.listen(port, () => {
    console.log('listening on port: ' + port);
});

domain.ts:

Within this file, the core logic of the domain is defined.

export type Entity = {
    param1: string,
    param2: string,
};

export type IRepository = {
    GetMultipleEntities(filterParam: string): Entity[] | undefined
    GetEntity(filterParam: string): Entity | undefined
    CreateEntity(entity: Entity): void
    UpdateEntity(entity: Entity): void
}

Of course, this is just one illustrative example to give you an idea of the project's structure.

Upon closer inspection, everything seems fine until we reach the api.ts file. It directly imports the concrete implementation and injects it into the use case. What if there are numerous dependencies to import and utilize? I don't want api.ts taking on the responsibility of deciding which implementations should be used where.

On the flip side, how should I go about implementing a composition root then? I'm uncertain about how to construct the complete object graph and then pass it to the server object so that the appropriate implementations end up in the right places.

Thank you in advance for any guidance!

Answer №1

Definitions

To provide clarity and insight into the concept of a Composition Root, let's refer to enlightening words by Mark Seemann found in his two informative articles on the subject: first and second.

Where do we construct object graphs?

Object graphs should be constructed as close to the application's entry point as possible.

What exactly is a Composition Root?

A Composition Root serves as a (ideally) singular point within an application where modules are orchestrated together.

The Composition Root functions as a core component of the application's infrastructure.

It is specific to each individual application, defining its unique characteristics. After implementing well-structured, loosely coupled code across your project, the Composition Root is where everything is finally integrated, from data access to user interfaces.

Implications

In essence, your api.ts could be considered the starting point of your server application, making it appropriate for composing your object graph within it. Alternatively, you could

  1. opt for server.ts or
  2. create a distinct DI module like composition-root.ts responsible for the composition process, then imported by either server.ts or api.ts (enhancing cohesion further).

The crucial aspect here is having a single, identifiable location near or at the entrance of your project that handles the creation and assembly of dependencies.

Example

Let's consider a practical scenario where all compositional tasks are carried out in composition-root.ts, which is later imported by api.ts. Your dependency graph takes the following form (--> signifies an import relationship):

server.ts --> api.ts --> application.ts --> domain.ts 
                                        --> sql-repository.ts

All components except composition-root.ts are decoupled from their dependencies. You could utilize constructor injection similar to the example in the article, or employ any other injection technique based on language, framework, or coding style preferences. Your existing setup appears well-structured, but incorporating a DB abstraction layer for the repository and relocating the composition logic away from api.ts would be beneficial.

sql-repository.ts:

export class SqlRepository implements IRepository {
  constructor(private db: DB) {}
  ...
}

api.ts:

import {CheckIfEntityExists} from "./composition-root"
...

router.get("/exists/:filterParam", async (req, res) => {
    CheckIfEntityExists(req.params.filterParam);
    res.end()
});

composition-root.ts:

import {CheckIfEntityExists} from './application';
import {SqlRepository} from './sql-repository';

const db = new DB();
const sqlRepository = new SqlRepository(db);
// Here, partial application is used to abstract the concrete repository from api.ts.
const _CheckIfEntityExists = (filterParam: string) =>
  CheckIfEntityExists(filterParam, sqlRepository);

export { _CheckIfEntityExists as CheckIfEntityExists };

Overall, consolidating dependencies in one central location like composition-root.ts while keeping inner layers of your application architecture unaware of their instantiation process is a commendable approach.

Trust this elucidates the concept better.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Rendering server applications using Angular 6 with Express

Currently, I am working with an Angular 6 application and Express server. I am looking to implement a server rendering system using the best practices available, but I have been struggling to find resources that are compatible with Angular 6 or do not util ...

Mongoose's hook function is effective in the pre-stage but ineffective in the post-stage

When using Mongoose hooks, I aim to automatically change the status property to false if the outstandingBalance property has a value of zero. Although attempting to achieve this with Mongoose's PRE hook works, it requires re-invoking the request afte ...

What is the best way to showcase the outcomes of arithmetic calculations on my calculator?

In the midst of creating a calculator, I have encountered some issues in getting it to display the correct result. Despite successfully storing the numbers clicked into separate variables, I am struggling with showing the accurate calculation outcome. l ...

Using JavaScript to assign function arguments based on arbitrary object values

I am facing a challenge with a collection of arbitrary functions and a method that takes a function name along with an object or array of parameters to call the respective function. The issue arises from the varying number of inputs in these functions, som ...

transferring expressjs cookie via JSON format

I came across the data in the express API reference expressjs documentation on cookies According to the documentation, cookies can be sent as JSON res.cookie('cart', { items: [1,2,3] }); So I decided to give it a try. The cookie worked fine wh ...

Disappearing Query Parameters in POST Requests with Node.js and Express

This is my first attempt at creating a JavaScript client and nodeJS server with Express that communicate using REST API. However, I'm running into an issue where any parameter I pass in the xhttp.send function is not being received on the back-end. O ...

Struggling to locate a suitable mock server that can deliver JSON responses for a specific URL that has been predetermined

I have encountered a challenge in my frontend app development using vue.js. I need to find a mock backend server (without mocking it on the front end). My app is capable of making HTTP requests, specifically GET and PATCH are the methods of interest. I am ...

Developing a Typescript module, the dependent module is searching for an import within the local directory but encounters an issue - the module cannot be found and

After creating and publishing a Typescript package, I encountered an issue where the dependent module was not being imported from the expected location. Instead of searching in node_modules, it was looking in the current folder and failing to locate the mo ...

What could be causing routerLink to malfunction despite correct configuration?

Is routerLink properly placed in the view? <p><a routerLink="/registration" class="nav-link">Register</a></p> Checking my app.module import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular ...

Unable to modify translation values in an existing ngx-translate object

I am facing an issue where I am unable to change the value of an object property within a translation JSON file using ngx translate. Despite attempting to update the value dynamically when receiving data from an API, it seems to remain unchanged. I have t ...

Update the function's argument type signature if the current argument is a function with properties

Looking for input on a potential title change, but for now, here are the details of my specific use case: I'm currently developing a library that facilitates executing methods remotely and accessing properties across serialized boundaries like those ...

Is there a way to assign values of object properties to the corresponding object in TypeScript?

I'm looking for a solution in TypeScript where I can map values of object keys to the same object, and have IntelliSense work correctly. Here's an example that illustrates what I need: const obj = getByName([ { __name: 'foo', baz: &ap ...

Enhancing the mongoose subSchema Element

In my programming schema, I have defined a subSchema and mainSchema as shown below: const subSchema = new Schema({ /*...*/ }) const mainSchema = new Schema({ //..., foo:{ type:subSchema, default:{} } }) const Model = model('Model' ...

Securing the public webpack bundle access in ExpressJS?

Within my webpack configuration, the publicPath is defined as follows: publicPath: '/js' This setting directs it to public/js. Additionally, in my server-loaded index.pug file (not located within the public folder), I have the following code: ...

Issue with Material UI v5: "spacing" property not found on custom theme object

My current setup involves using version 5 of material ui, where I have customized a theme and applied it to all my components. However, when trying to add padding to a paper element in one of my components based on the theme, I encountered the following e ...

Solving Problems with Inline Tables using Angular, Express, and Mongoose's PUT Method

For the past few days, I've been struggling to figure out why my PUT request in my angular/node train schedule application isn't functioning properly. Everything else - GET, POST, DELETE - is working fine, and I can successfully update using Post ...

The deployment of Crawlee on GCP resulted in a BrowserLaunchError

I've been experimenting with deploying a simple crawler application using an Express server. I came across this guide - , and utilizing PlaywrightCrawler where persistStorage is set to false. Although the application deploys successfully, I encounter ...

Local working fine but encountering issues on Openshift, specifically with syncing npm package versions across environments (local and global)

As I develop a forum using Angular that connects with Node/Mongo, there are numerous requests required to populate the page with necessary data. While everything functions flawlessly locally, once uploaded to Openshift, the site displays various bugs and f ...

Navigating through Node and Express on Azure App Service

I'm facing an issue that I am not sure if it is related to Node or Azure App Service, so here's the situation: In my Node/Express app, I have defined two routes: router.get("/users", checkAuthHeader, userController.getUsers); router.po ...

Utilizing nodemon alongside express js during npm start: A user guide

Looking to utilize nodemon for automatically recognizing changes in my node.js project scripts and restarting when detected. My project is structured with express.js. Interested in knowing how to integrate nodemon with express.js so that it automatically ...