Skip to content

Application Base Classes

Overview

@nx/core provides one abstract base class, BaseApplication, that holds the infrastructure every BANA backend service shares: CORS and body-limit middleware, the health check endpoint, Swagger/OpenAPI documentation, static file serving, IdentityNetworkService registration, and the preConfigure() initialization flow.

Services do not extend BaseApplication directly. They extend one of two concrete subclasses based on their role in the JWT trust chain:

  • IssuerApplication - extended only by @nx/identity. Signs JWTs (JWKS issuer) and serves the public key set at /jw-certs.
  • VerifierApplication - extended by the other 13 IGNIS services. Verifies JWTs against identity's JWKS endpoint.

A third subclass, DefaultApplication, is legacy and unused - nothing extends it. It predates the Issuer/Verifier split and uses a symmetric JWS token with no authorization.

Source:

FileClass
packages/core/src/application/base.tsBaseApplication (abstract, shared)
packages/core/src/application/issuer.tsIssuerApplication
packages/core/src/application/verifier.tsVerifierApplication
packages/core/src/application/default.tsDefaultApplication (legacy)

See the IGNIS Application reference for details on the IGNIS BaseApplication that @nx/core's base extends.

Class Hierarchy

Base classExtended byAuthenticationAuthorization
BaseApplication (abstract)- (shared by all)declares configureAuthentication()declares configureAuthorization()
IssuerApplication@nx/identity onlyJWKS issuer (signs, serves /jw-certs)Casbin (scoped)
VerifierApplicationcommerce, finance, inventory, sale, signal, payment, pricing, ledger, invoice, licensing, outreach, taxation, helpdeskJWKS verifier (validates against identity JWKS)Casbin (scoped)
DefaultApplicationnothing (legacy)symmetric JWSnone

Class Definition

BaseApplication is abstract: it implements the shared lifecycle and leaves authentication/authorization to its subclasses.

typescript
import { BaseApplication as IgnisBaseApplication, IApplicationInfo } from '@venizia/ignis';
import { ValueOrPromise } from '@venizia/ignis-helpers';

export abstract class BaseApplication extends IgnisBaseApplication {
  protected applicationRoles: string[] = [];

  // Implemented per subclass (Issuer / Verifier / Default)
  protected abstract configureAuthentication(): ValueOrPromise<void>;
  protected abstract configureAuthorization(): ValueOrPromise<void>;

  // Shared lifecycle
  override getAppInfo(): IApplicationInfo;
  preConfigure(): void;
  async postConfigure(): Promise<void>;
  override async setupMiddlewares(): Promise<void>;
  staticConfigure(): void;

  // Registration methods (override in child packages)
  configureDatasources(): void;
  configureRepositories(): void;
  configureServices(): void;     // registers IdentityNetworkService
  configureComponents(): void;   // HealthCheck + Swagger
  configureSecurity(): void;     // -> configureAuthentication() + configureAuthorization()
  configureControllers(): void;
}

Lifecycle and Boot Sequence

preConfigure() Call Order

preConfigure() runs the registration methods in a fixed order. In MIGRATE run mode it short-circuits after repositories (a migration only needs database access); otherwise it continues through services, components, security, and controllers.

typescript
preConfigure() {
  this.applicationRoles = this.getApplicationRoles();

  this.configureDatasources();   // 1. Data sources
  this.configureRepositories();  // 2. Repositories

  if (this.getApplicationRunMode() !== ApplicationRunModes.MIGRATE) {
    this.configureServices();      // 3. Services (IdentityNetworkService)
    this.configureComponents();    // 4. Components (HealthCheck, Swagger)
    this.configureSecurity();      // 5. Authentication + Authorization
    this.configureControllers();   // 6. Controllers
  }
}

Full Boot Sequence

Middleware Configuration

setupMiddlewares() lives in the shared base. It optionally wires an HTTP tracing middleware (only when telemetry was initialized by bootstrapApplication), then registers CORS and a body limit.

CORS

typescript
cors: {
  enable: true,
  path: '*',
  origin: '*',
  allowMethods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'],
  allowHeaders: [/* see table below */],
  exposeHeaders: [/* see table below */],
  maxAge: 86_400,  // 24 hours
  credentials: true,
}

Allowed Request Headers:

HeaderIGNIS Constant
AuthorizationHTTP.Headers.AUTHORIZATION
Content-TypeHTTP.Headers.CONTENT_TYPE
X-Request-ChannelHTTP.Headers.REQUEST_CHANNEL
X-Count-DataHTTP.Headers.REQUEST_COUNT_DATA
X-Device-InfoHTTP.Headers.REQUEST_DEVICE_INFO
X-Tracing-IdHTTP.Headers.REQUEST_TRACING_ID
timezoneCustom
timezone-offsetCustom
x-auth-providerCustom
x-forward-forCustom
x-localeCustom
x-merchant-idCustom (active-merchant scope)
x-real-ipCustom

Exposed Response Headers:

HeaderIGNIS Constant
Content-DispositionHTTP.Headers.CONTENT_DISPOSITION
Content-LengthHTTP.Headers.CONTENT_LENGTH
Content-RangeHTTP.Headers.CONTENT_RANGE
Content-TypeHTTP.Headers.CONTENT_TYPE
X-Count-DataHTTP.Headers.RESPONSE_COUNT_DATA
X-Response-FormatHTTP.Headers.RESPONSE_FORMAT
x-custom-headerCustom

Body Limit

typescript
bodyLimit: {
  enable: true,
  path: '*',
  maxSize: 100 * 1024 * 1024,  // 100 MB
  onError: c => c.json({}, HTTP.ResultCodes.RS_4.ContentTooLarge),  // HTTP 413
}

Component Setup

Health Check

Registered at the /health endpoint. Returns application status information.

typescript
this.bind<IHealthCheckOptions>({
  key: HealthCheckBindingKeys.HEALTH_CHECK_OPTIONS,
}).toValue({
  restOptions: { path: '/health' },
});
this.component(HealthCheckComponent);

See the IGNIS Health Check reference for response format and customization.

Swagger / OpenAPI

Provides three endpoints for API documentation, using the Swagger UI renderer (ui.type: 'swagger'):

PathDescription
/docDocumentation base path
/openapi.jsonOpenAPI 3.0 specification (JSON)
/explorerSwagger UI
typescript
this.bind<ISwaggerOptions>({ key: SwaggerBindingKeys.SWAGGER_OPTIONS }).toValue({
  restOptions: {
    base: { path: '/doc' },
    doc: { path: '/openapi.json' },
    ui: { path: '/explorer', type: 'swagger' },
  },
  explorer: {
    openapi: '3.0.0',
    info: {
      title: 'API Documentation',
      version: this.getAppInfo().version,
      description: 'NEXPANDO - Seller API documentation',
    },
    servers: [
      {
        url: applicationEnvironment.get<string>(EnvironmentKeys.APP_ENV_APPLICATION_EXPLORER_URL),
        description: 'Application Server URL',
      },
    ],
  },
});
this.component(SwaggerComponent);

See the IGNIS Swagger reference for full options.

Security Setup

configureSecurity() is shared by the base and simply delegates to the two abstract methods, which each subclass implements:

typescript
configureSecurity(): void {
  this.configureAuthentication();
  this.configureAuthorization();
}

Authentication per Subclass

SubclassJWT strategyJOSEStandardsNotes
IssuerApplicationJWKSIssuerAuthenticationStrategyJWKS (mode ISSUER)Signs with a PEM private key; publishes keys at /jw-certs
VerifierApplicationJWKSVerifierAuthenticationStrategyJWKS (mode VERIFIER)Validates against APP_ENV_IDENTITY_SERVICE_BASE_URL + /jw-certs
DefaultApplicationJWSAuthenticationStrategyJWSLegacy symmetric secret; not used by any service

All three call the shared bindBasicAuthentication() helper, register AuthenticateComponent, and add a Basic strategy that delegates to the Identity service.

Basic Authentication (shared)

bindBasicAuthentication() (defined on the base) delegates credential verification to the Identity service via IdentityNetworkService.signIn():

typescript
protected bindBasicAuthentication(): void {
  this.bind<TBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS }).toValue({
    verifyCredentials: ({ credentials }) => {
      const identityNetworkService = this.get<IdentityNetworkService>({
        key: BindingKeys.build({
          namespace: BindingNamespaces.SERVICE,
          key: IdentityNetworkService.name,
        }),
      });

      return identityNetworkService.signIn({
        identifier: { scheme: 'username', value: credentials.username },
        credential: { scheme: 'basic', value: credentials.password },
      });
    },
  });
}

Authorization (Casbin)

IssuerApplication and VerifierApplication both register the IGNIS AuthorizeComponent with a scoped Casbin enforcer (ScopedCasbinAdapter, domain-scoped model, defaultDecision: DENY, alwaysAllowRoles = SUPER_ADMIN / ADMIN / OPERATOR) and a merchant-scoped domain resolver driven by the x-merchant-id header. DefaultApplication.configureAuthorization() is empty.

Authentication Flow

See the IGNIS Authentication reference for strategy details.

Static File Serving

staticConfigure() serves files from the public/ directory of the @nx/core package:

typescript
staticConfigure(): void {
  this.static({ folderPath: path.join(__dirname, '../../public') });
}

How to Extend in Child Packages

Every service declares an Application class that extends VerifierApplication (or IssuerApplication for @nx/identity) and overrides the registration methods, calling super first:

typescript
// packages/sale/src/application.ts
import { VerifierApplication, PostgresCoreDataSource } from '@nx/core';

export class Application extends VerifierApplication {
  override configureDatasources(): void {
    super.configureDatasources();
    this.dataSource(PostgresCoreDataSource);
  }

  override configureRepositories(): void {
    super.configureRepositories();
    this.repository(SaleOrderRepository);
    this.repository(SaleOrderItemRepository);
  }

  override configureServices(): void {
    super.configureServices(); // IdentityNetworkService
    this.service(SaleOrderService);
    this.service(CheckoutService);
  }

  override async postConfigure(): Promise<void> {
    await super.postConfigure();
    // Run after all components are bound
  }
}

Always call super

When overriding any registration method, call super.<method>() first to preserve the default registrations (IdentityNetworkService, HealthCheck, Swagger, JWKS/Basic auth).

Environment Variables

VariableUsed byRequiredDescription
APP_ENV_APPLICATION_SECRETAllYesApplication-level secret
APP_ENV_JWKS_ALGORITHMIssuerNoSigning algorithm (default ES256)
APP_ENV_JWKS_PRIVATE_KEYIssuerYesPEM private signing key
APP_ENV_JWKS_PUBLIC_KEYIssuerYesPEM public key
APP_ENV_JWKS_REST_PATHIssuer + VerifierNoJWKS endpoint path (default /jw-certs)
APP_ENV_IDENTITY_SERVICE_BASE_URLVerifier (+ Basic)YesIdentity service base URL (JWKS source + Basic delegation)
APP_ENV_JWT_EXPIRES_INIssuerYesToken expiration in seconds
APP_ENV_APPLICATION_ROLESAllNoComma-separated role identifiers
APP_ENV_APPLICATION_EXPLORER_URLAllNoServer URL shown in Swagger explorer

Legacy: DefaultApplication

DefaultApplication (application/default.ts) is retained only for reference. It configures a symmetric JWS token (APP_ENV_JWT_SECRET) via JWSAuthenticationStrategy and leaves configureAuthorization() empty. No service extends it - every service uses IssuerApplication or VerifierApplication instead. Do not use it for new packages.

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.