The Application Class
The Application class orchestrates your application's configuration, lifecycle, and resource registration (components, controllers, services).
Deep Dive: See Application Reference for technical details.
Creating an Application
Extend BaseApplication and implement the hook methods:
// src/application.ts
import {
BaseApplication,
IApplicationConfigs,
IApplicationInfo,
ValueOrPromise,
} from "@venizia/ignis";
import AutoArtifacts from "@venizia/ignis-boot/auto";
import packageJson from "./../package.json";
// Application configurations
export const appConfigs: IApplicationConfigs = {
host: process.env.APP_ENV_SERVER_HOST,
port: +(process.env.APP_ENV_SERVER_PORT ?? 3000),
path: {
base: process.env.APP_ENV_SERVER_BASE_PATH ?? "/",
isStrict: true,
},
debug: {
shouldShowRoutes: process.env.NODE_ENV !== "production",
},
// Registers every decorated datasource, component, repository, service and controller
artifacts: AutoArtifacts,
};
// Main Application class
export class Application extends BaseApplication {
override getAppInfo(): ValueOrPromise<IApplicationInfo> {
return packageJson;
}
staticConfigure(): void {
// e.g., this.static({ folderPath: './public' })
}
preConfigure(): ValueOrPromise<void> {
// Setup a decorator cannot express, e.g. bind a value or an option
}
postConfigure(): ValueOrPromise<void> {
// Logic to run after everything is configured
}
setupMiddlewares(): ValueOrPromise<void> {
// Add any custom application-wide middlewares
}
}Application Lifecycle
The IGNIS application has a well-defined lifecycle, managed primarily by the init(), start() and initialize() methods.
| Method | Description |
|---|---|
constructor(opts) | Initializes the application, sets up the Hono server (OpenAPIHono), and detects the runtime (Bun/Node). The Application extends Container (IoC container). |
init() | Registers the core bindings, including CoreBindings.APPLICATION_INSTANCE. Call it before start(). |
start() | The main entry point. It calls initialize(), sets up middlewares, mounts the root router, and starts the HTTP server. |
stop() | Stops the application server (calls Bun.serve.stop() or node-server.close()), then closes every datasource the boot configured, so no pool, socket or PGlite instance outlives the application. |
initialize() | Orchestrates the entire setup process, calling the various configuration and registration methods in the correct order. |
WARNING
start() does not call init() for you, and nothing else does either. init() is the only caller of registerCoreBindings(), which binds CoreBindings.APPLICATION_INSTANCE. Every built-in component injects that key, so an application started without init() throws Binding key: @app/instance is not bounded in context! on the first component resolution.
const application = new Application({ scope: 'MyApp', config: appConfigs });
application.init();
await application.start();The BaseApplication class provides several overridable hook methods that allow you to customize the startup process. These are the primary places you'll write your application-specific setup code.
| Hook Method | Purpose |
|---|---|
getAppInfo() | Required. Return application metadata, usually from package.json. Used for OpenAPI docs. |
staticConfigure() | Configure static file serving. Called before preConfigure(). |
preConfigure() | Register what the artifact index cannot express - registry calls, hand-made bindings. Classes come from configs.artifacts, see Registering artifacts. |
postConfigure() | Perform actions after all resources have been configured and instantiated. Note: do not bind new datasources, components, or controllers here -- they will not be auto-configured. |
setupMiddlewares() | Add custom application-level middlewares to the Hono instance. Called after initialize() completes. |
Lifecycle Diagram
This diagram shows the sequence of operations during application startup. initialize() runs a boot sequence of sixteen named steps, always in this order.
init() ← You call this. Binds CoreBindings.APPLICATION_INSTANCE
│
▼
start()
│
▼
initialize()
│
├─► 1. printStartUpInfo (application name, version, environment)
├─► 2. validateEnvs
├─► 3. registerDefaultMiddlewares (request id, error handler, 404, contextStorage, favicon)
├─► 4. staticConfigure ← Override hook
├─► 5. registerArtifacts (registers the index named by configs.artifacts)
├─► 6. preConfigure ← Override hook
├─► 7. hydrateSecrets (pulls secrets from the bound provider)
├─► 8. registerConfigurations
├─► 9. registerDataSources
├─► 10. registerComponents
├─► 11. registerContributedDataSources (datasources a component added)
├─► 12. wireSecretRotatables (subscribes rotatable datasources to rotation)
├─► 13. registerControllers (REST + gRPC transport components)
├─► 14. postConfigure ← Override hook
├─► 15. verifyBindings (only when bootChecks.binding.doVerify is on)
└─► 16. validateScopeFilterSupport
│
▼
setupMiddlewares() ← Override hook
│
▼
Mount Root Router (server.route(basePath, rootRouter))
│
▼
Start HTTP Server (Bun.serve or @hono/node-server)
│
▼
executePostStartHooks()The step names above are the vocabulary you target with BootSequence.insertAfter. They live on ServerBootSteps, which extends the kernel's BootSteps.
Configuration
Application configuration is passed to the BaseApplication constructor via an IApplicationConfigs object.
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
host | string | 'localhost' | The host address. Falls back to HOST or APP_ENV_SERVER_HOST env vars. |
port | number | 3000 | The port to listen on. Falls back to PORT or APP_ENV_SERVER_PORT env vars. |
path.base | string | '/' | The base path for all application routes (e.g., /api). |
path.isStrict | boolean | true | When true, /users and /users/ are different routes. Set false to answer both, and declare routes without a trailing slash: under false, a route declared as /users/ is unreachable. |
debug.shouldShowRoutes | boolean | false | If true, prints all registered routes to the console on startup. |
favicon | string | '🔥' | An emoji to be used as the application's favicon. |
artifacts | TArtifactIndexInput | undefined | The generated artifact index (or several) to register before preConfigure(). See Registering artifacts. |
asyncContext.enable | boolean | true | Enable Hono's async context storage (powered by contextStorage()). |
transports | TControllerTransport[] | ['rest'] | Controller transports to enable. Add 'grpc' for gRPC support. |
error.rootKey | string | undefined | Optional root key for error response wrapping. |
Example Configuration
import { ControllerTransports } from '@venizia/ignis';
export const appConfigs: IApplicationConfigs = {
host: "0.0.0.0",
port: 3000,
path: {
base: "/api",
isStrict: true,
},
debug: {
shouldShowRoutes: true,
},
transports: [ControllerTransports.REST, ControllerTransports.GRPC],
};Registering Resources
Decorate each class and set artifacts: AutoArtifacts in the application config. IGNIS registers every decorated class before preConfigure() runs. See Registering artifacts.
| Decorator | Marks | Binding Scope |
|---|---|---|
@datasource({ ... }) | A database connection | Singleton |
@component() | A reusable module | Singleton |
@repository({ model, dataSource }) | Data access | Transient |
@service() | Business logic | Transient |
@controller({ path }) | API endpoints | Singleton |
Registering by hand
preConfigure() is for what a decorator cannot express. The this.dataSource(...), this.component(...), this.repository(...), this.service(...) and this.controller(...) methods still work as an escape hatch. They fail when bootChecks.binding.allowManual is false.
All of them accept an optional second parameter to customize the binding key:
// Default binding (key: 'controllers.UserController')
this.controller(UserController);
// Custom binding key
this.controller(UserController, {
binding: { namespace: 'controllers', key: 'CustomUserController' }
});Registration order for decorated classes:
- Configurations
- DataSources (database connections)
- Components
- Repositories (depend on DataSources)
- Services (depend on Repositories)
- Controllers (depend on Services)
Deep Dive: See Dependency Injection for how registration and injection work together.
Runtime Support
The application detects the runtime automatically via RuntimeModules.detect():
// Bun (default) - uses Bun.serve
Bun.serve({ port, hostname, fetch: server.fetch })
// Node.js - requires @hono/node-server
import { serve } from '@hono/node-server'
serve({ fetch: server.fetch, port, hostname })Post-Start Hooks
Register hooks that execute after the HTTP server has started:
this.registerPostStartHook({
identifier: 'cache-warmup',
hook: async () => {
// Warm up caches, start background jobs, etc.
},
});Static File Serving
Serve static files with runtime-aware implementation:
staticConfigure(): void {
this.static({ restPath: '/public/*', folderPath: './public' });
}Uses hono/bun serveStatic for Bun runtime, @hono/node-server/serve-static for Node.js.
See Also
Related Concepts:
- Registering artifacts - Decorators, the generated index,
configs.artifacts - REST Controllers | gRPC Controllers - Creating HTTP/gRPC endpoints
- Services - Business logic layer
- Dependency Injection - How DI works in IGNIS
- Registering artifacts - Decorators, the generated index,
References:
- BaseApplication API - Complete API reference
- Environment Variables - Configuration management
Tutorials:
- 5-Minute Quickstart - Create your first app
- Building a CRUD API - Complete application example