Spraxium logoSpraxium

Configuration and Single-Module Setup

Configure @spraxium/http with one global ownership model and predictable startup behavior. Keep HTTP metadata attached to AppModule and register the plugin through a single config entry.

Configuration ownership first

The most important architecture decision is where metadata lives. HttpServer reads HttpClientModule metadata from the class referenced in defineHttp({ module }), so that class becomes the single owner of every controller, service, guard, and middleware declaration in the HTTP surface. Using AppModule as that class keeps ownership explicit and prevents silent configuration drift when teams refactor modules.

No local HTTP wrapper module needed

Avoid creating a separate AppHttpModule only to host HttpClientModule metadata. Keep HttpClientModule on AppModule, import HttpModule in AppModule, and reference AppModule in defineHttp. A second module with its own HttpClientModule is silently ignored by the server because only the root reference is resolved.

This model removes ownership ambiguity. Server lifecycle, guards, middleware, and route registration all point to one root class and one plugin entry. When you add a new controller or change the guard stack, the change is visible in one place and takes effect across the entire API surface without touching configuration files.

HttpConfig contract

HttpConfig is the interface that defineHttp() accepts and ConfigStore delivers to HttpServer at boot time. Every field maps to a specific runtime behavior, so understanding the contract prevents surprises during deployment.

SecurityConfig substructure

The security field accepts a SecurityConfig object with four optional subfields. Each subfield controls one layer of the hardening stack and can be set to false to disable it entirely. When a subfield is omitted, the package applies SECURITY_DEFAULTS automatically.

Keep the defineHttp call in a dedicated config file so network and security decisions are visible without scrolling through module decorators. The config file imports AppModule, and spraxium.config.ts imports the config file. This three-file pattern gives each concern a stable home: the config file owns infrastructure, the module owns metadata, and the root config owns plugin registration.

import { defineHttp } from '@spraxium/http';
import { AppModule } from '../src/app.module';

export const httpPlugin = defineHttp({
  enabled: true,
  host: process.env.HTTP_HOST ?? '0.0.0.0',
  port: Number(process.env.HTTP_PORT ?? 3000),
  apiKey: process.env.HTTP_API_KEY ?? '',
  sharding: false,
  security: {
    hidePoweredBy: true,
    headers: {
      frameOptions: 'DENY',
      contentSecurityPolicy: "default-src 'none'",
      referrerPolicy: 'no-referrer',
    },
    cors: {
      origins: ['https://admin.example.com'],
      methods: ['GET', 'POST', 'PATCH'],
      allowedHeaders: ['Content-Type', 'Authorization', 'X-Api-Key'],
      credentials: true,
    },
    bodyLimit: { maxSize: 256_000 },
  },
  module: AppModule,
});

HttpClientModule metadata shape

@HttpClientModule is a class decorator that stores metadata consumed by HttpServer during route registration. Each field controls a different phase of the startup pipeline.

The split between middleware and middlewareProviders gives clear ownership between global pipeline policy and route-level declarations. Global middleware runs on every request unconditionally. Route middleware runs only on endpoints decorated with @HttpUseMiddleware and is resolved lazily from the provider map, so unreferenced middleware classes never execute.

Setup sequence

Extract plugin configuration into a dedicated file

Keep the defineHttp call in config/http.config.ts so network binding, security policy, and sharding mode are visible in one place. This also keeps spraxium.config.ts focused on plugin registration instead of implementation details. Environment variables for host, port, and API key should be read here.

Point module to your real composition root

Set module: AppModule in defineHttp so HttpServer reads metadata from the same class that owns the module graph. Creating a separate HttpAppModule leads to drift because changes in AppModule do not propagate to the HTTP metadata source automatically.

Import HttpModule once in AppModule

HttpModule is global and should appear once in the root module imports. That ensures one lifecycle owner for server startup and graceful shutdown. Importing it in multiple modules does not cause errors but adds no benefit.

Register guards and middleware in HttpClientModule

Declare custom guard classes in the @HttpClientModule() array for global protection. The framework resolves each guard class via DI at startup. Middleware instances go in middleware and run after the security stack. Route-level middleware goes in middlewareProviders and is activated per-endpoint with @HttpUseMiddleware.

Register one HTTP plugin entry in defineConfig

Keep a single HTTP plugin entry inside defineConfig({plugins}). Multiple entries lead to ambiguous ownership and unstable route registration because ConfigStore returns only one config for the defineHttp key.

Startup behavior by mode

ModeWhen it startsClient requirement

Bridge implementation

Direct, with sharding: false

During onBoot lifecycle hook, after the Discord Client is ready.

Required. If missing, server logs an error and skips startup.

DirectBotBridge: accesses guild cache and Discord API through the client instance directly.

Sharded, with sharding: true

Via ParentHookRegistry when the parent process creates the ShardingManager.

Not required. The ShardingManager replaces the client for bot interactions.

ShardedBotBridge: uses manager.broadcastEval() to execute operations on the shard that owns each guild.

In direct mode, the server has immediate access to the Discord Client and can inject it into the DI map alongside BotBridge. Controllers that need the client directly (for features not covered by the bridge) can declare it as a constructor parameter. In sharded mode, the client is not available in the HTTP process, so controllers must use BotBridge exclusively for Discord interactions.