SkillAgentSearch skills...

Crux

Node.js backend package including: framework (NestJS), HTTP server (Fastify), HTTP client (Fetch), distributed caching (ioredis), ORM (MikroORM), swagger documentation (Redoc), logger (Loki), metrics (Prometheus) and tracing (Tempo with OpenTelemetry).

Install / Use

/learn @etienne-bechara/Crux

README

CRUX

Quality Gate Status Coverage Maintainability Rating Reliability Rating Security Rating

CRUX is an opinionated Node.js framework package designed for backend projects. It integrates a range of libraries and patterns commonly used in distributed, stateless applications that require database access.


Disclaimer

This framework was created to avoid rebuilding similar boilerplates for distributed, stateless backend projects with database access. It is highly opinionated and should be treated more as a reference for creating your own solutions rather than as a production-ready product.


Installation

  1. Create and initialize a new Node.js project, then install TypeScript, its types, a live-reload tool, and this package.

    We recommend using pnpm as your package manager, and ts-node-dev for live reloading:

    mkdir my-project
    cd my-project
    
    git init
    npm init -y
    
    npm i -g pnpm
    pnpm i -D typescript @types/node ts-node-dev
    pnpm i -E @bechara/crux
    
    tsc --init
    
  2. Create a main.ts file in a /source folder with the following content:

    // /source/main.ts
    import { AppModule } from '@bechara/crux';
    
    void AppModule.boot();
    
  3. Add a dev script in your package.json:

    {
      "scripts": {
        "dev": "tsnd --exit-child --rs --watch *.env --inspect=0.0.0.0:9229 ./source/main.ts"
      }
    }
    
  4. Start the application:

    pnpm dev
    

    You can test it by sending a request to GET /. You should receive a successful response with a 204 status code.


Development

Using this framework mostly follows the official NestJS Documentation. Familiarize yourself with the following core NestJS concepts before continuing:

Key Differences

  1. Imports from @bechara/crux
    All NestJS imports, such as @nestjs/common or @nestjs/core, are re-exported by @bechara/crux.
    Instead of:

    import { Injectable } from '@nestjs/common';
    

    use:

    import { Injectable } from '@bechara/crux';
    
  2. Automatic Module Loading
    Any file ending with *.module.ts in your source folder is automatically loaded by main.ts. You don’t need to create a global module importing them manually.
    Instead of:

    @Global()
    @Module({
      imports: [
        FooModule,
        BarModule,
        BazModule,
      ],
    })
    export class AppModule { }
    

    simply do:

    import { AppModule } from '@bechara/crux';
    
    void AppModule.boot();
    // FooModule, BarModule, and BazModule are automatically loaded
    // as long as they're in the source folder and named *.module.ts
    

Testing

Testing can involve multiple environment variables, making it more complex to write boilerplate code. For this reason, AppModule offers a built-in compile() method to create an application instance without serving it.

Usage

In your *.service.spec.ts, add a beforeAll() hook to compile an application instance:

import { AppModule } from '@bechara/crux';

describe('FooService', () => {
  let fooService: FooService;

  beforeAll(async () => {
    const app = await AppModule.compile();
    fooService = app.get(FooService);
  });

  describe('readById', () => {
    it('should read a foo entity', async () => {
      const foo = await fooService.readById(1);
      expect(foo).toEqual({ name: 'bob' });
    });
  });
});

If you need custom options, the compile() method supports the same boot options as boot().

Run all tests with:

pnpm test

Or a specific set:

pnpm test -- foo

Curated Modules

Below are details about the main modules in this framework and how to use them.

Application Module

Acts as the entry point, wrapping other modules in this package and automatically loading any *.module.ts in your source folder.

By default, it serves an HTTP adapter using Fastify. The following custom enhancers are globally applied:

Environment Configuration

| Variable | Mandatory | Type | |-----------|:--------:|--------------------------------------------------------| | NODE_ENV | Yes | AppEnvironment |

Module Options

When booting your application, you can configure options as described in AppBootOptions:

import { AppModule } from '@bechara/crux';

void AppModule.boot({
  // See AppBootOptions for detailed properties
});

Provided options will be merged with the default configuration.


Configuration Module

Allows asynchronous population of secrets through *.config.ts files containing configuration classes.

Decorate a class with @Config() to make it available as a regular NestJS provider. Any property decorated with @InjectSecret() will have its value extracted from process.env and injected into the class.

Usage

  1. Create a *.config.ts file with a class decorated by @Config().
  2. Decorate any properties with @InjectSecret().
  3. Optionally, apply class-validator and class-transformer decorators for validation and transformation.

Example:

import { Config, InjectSecret, IsUrl, IsString, Length, ToNumber } from '@bechara/crux';

@Config()
export class FooConfig {
  @InjectSecret()
  @IsUrl()
  FOO_API_URL: string;

  @InjectSecret({ key: 'foo_authorization' })
  @IsString() @Length(36)
  FOO_API_KEY: string;

  @InjectSecret({ fallback: '15' })
  @ToNumber()
  FOO_API_MAX_CONCURRENCY: number;
}

Use the configuration in your module and services:

@Injectable()
export class FooService {
  constructor(private readonly fooConfig: FooConfig) {}

  public async readFooById(id: number) {
    console.log(this.fooConfig.FOO_API_MAX_CONCURRENCY);
    // ...
  }
}

Context Module

Provides ContextService, an alternative to REQUEST-scoped injections in NestJS. It leverages Node.js AsyncLocalStorage to store request data without the performance or dependency-resolution challenges of REQUEST scope.

Usage

import { ContextService } from '@bechara/crux';

@Injectable()
export class FooService {
  constructor(private readonly contextService: ContextService) {}

  public getRequestAuthorization() {
    const req = this.contextService.getRequest();
    return req.headers.authorization;
  }

  public getUserId() {
    return this.contextService.getMetadata('userId');
  }

  public setUserId(userId: string) {
    this.contextService.setMetadata('userId', userId);
  }
}

Documentation Module

Generates OpenAPI documentation using NestJS OpenAPI Decorators.

  • User interface: available at /docs
  • OpenAPI spec: available at /docs/json

Http Module

Provides a wrapper over Node.js Fetch API, exposing methods to make HTTP requests. Its scope is transient: every injection yields a fresh instance.

Basic Usage

In your module:

import { HttpModule } from '@bechara/crux';

@Module({
  imports: [HttpModule.register()],
  controllers: [FooController],
  providers: [FooService],
})
export class FooModule {}

In your service:

import { HttpService } from '@bechara/crux';

@Injectable()
export class FooService {
  constructor(private readonly httpService: HttpService) {}

  public async readFooById(id: number) {
    return this.httpService.get('https://foo.com/foo/:id', {
      replacements: { id },
    });
  }
}

Async Registration

To configure base parameters (host, headers, API keys, etc.) using environment secrets:

import { HttpAsyncModuleOptions, HttpModule } from '@bechara/crux';

const httpModuleOptions: HttpAsyncModuleOptions = {
  inject: [FooConfi
View on GitHub
GitHub Stars14
CategoryDevelopment
Updated5mo ago
Forks2

Languages

TypeScript

Security Score

92/100

Audited on Oct 25, 2025

No findings