Leadline Architecture Design

Extensibility and tooling

Inspecting the IoC container, configurable dynamic modules, mixins, schematics, and Nest CLI scaffolding.

Accessing the IoC container

Libraries and internal tooling can discover providers and controllers at runtime using DiscoveryService, ModulesContainer, and Reflector. That enables patterns such as custom schedulers, convention-based registration, or metadata-driven features similar in spirit to @nestjs/schedule or GraphQL resolver discovery.

Discovery flow (conceptual):

  1. On module init, read the modules container and flatten InstanceWrapper collections for providers or controllers.
  2. Use Reflector.getAllAndOverride (or related APIs) on class tokens, constructors, or method handles to read metadata from custom decorators.
  3. Wire behavior (intervals, handlers, routes) from discovered metadata.

Practices: run discovery during startup, not per request; validate metadata defensively; clean up timers, subscriptions, or workers in onModuleDestroy; avoid heavy work in discovery paths.

Configurable module builder

ConfigurableModuleBuilder from @nestjs/common reduces boilerplate for dynamic modules by generating register, registerAsync, forRoot, or custom-named factory methods, optional extras (such as global flags), and a typed options token.

import { ConfigurableModuleBuilder } from "@nestjs/common";

const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder()
  .setClassMethodName("register")
  .build();

export class HttpClientModule extends ConfigurableModuleClass {}

Consumers then call HttpClientModule.register({ baseUrl: '...' }) or registerAsync with factories that inject ConfigService and other dependencies.

Practices: strong TypeScript interfaces for options; document sync vs async registration; test both paths; use extras for module-level flags that are not passed to internal providers.

Mixins

Mixins compose behavior by returning a subclass of a base constructor—useful when TypeScript’s single inheritance is limiting but you want shared, reusable fragments (validation pipes, identifiers, cross-cutting helpers).

Conventions: suffix files with .mixin.ts; constrain generics so the base constructor is compatible with Nest DI; keep mixin surfaces small and well typed.

Parameterized pipe (illustrative)

import { Inject, Injectable, NotFoundException, PipeTransform, Type } from "@nestjs/common";
import { getRepositoryToken } from "@nestjs/typeorm";
import type { Repository } from "typeorm";

export function entityExistsPipe(entity: Type<unknown>) {
  @Injectable()
  class EntityExistsPipe implements PipeTransform {
    constructor(
      @Inject(getRepositoryToken(entity)) private readonly repository: Repository<unknown>,
    ) {}

    async transform(value: unknown) {
      const exists = await this.repository.exists({ where: { id: value } as object });
      if (!exists) {
        throw new NotFoundException(`${entity.name} not found`);
      }
      return value;
    }
  }

  return EntityExistsPipe;
}

Schematics and the Nest CLI

Schematics are template-based transformers used by the Nest CLI (nest generate module|controller|service|...). They enforce structure, reduce boilerplate, and can be extended for organization-specific generators.

Custom schematics can orchestrate multiple steps: compose built-in generators, emit files from templates, and update module classes—for example alongside configurable module patterns.

Generating an application

Typical bootstrap:

nest new my-api
cd my-api
nest generate module items
nest generate controller items
nest generate service items

REST resources in one step:

nest generate resource items --type rest

Generated layouts favor speed and Nest defaults, not hexagonal boundaries—treat output as a starting point and refactor toward ports and adapters as the domain grows.

  • Architecture — where extensibility patterns land in folder structure.
  • Debugging — when discovery or dynamic registration misbehaves.

This page and links

Loading map…

On this page