Skip to main content
zerotal

Documentation


Documentation / zerotal / index / Application

Class: Application

Defined in: packages/core/src/application/Application.ts:290

The application kernel: container, provider lifecycle, routing, and server.

Application is the process-wide singleton that owns the IoC Container, registers and boots ServiceProviders through the framework lifecycle, loads configuration and routes, and binds the Bun HTTP server. Build one with the fluent Application.create factory, chain configuration/routing calls, then start (web) or bootAsWorker (queue worker).

Remarks

The full lifecycle is: boot() runs phases REGISTERING → BOOTING → BOOTED (config, provider graph, routes, conventions); start() then runs STARTING → STARTED and binds Bun.serve(); stop()/close() run STOPPING → STOPPED in LIFO order. start() boots first if it has not already.

Example

// bootstrap/app.ts
import { Application } from "@zerotal/core";

export const app = Application.create({ providers: [AppServiceProvider] })
  .useConfig(configLoader("./config"))
  .routing({ web: "./routes/web.ts", api: "./routes/api.ts" })
  .use([CorsMiddleware]);

await app.start(3000); // boots, then serves on :3000

Configuration

registerConfigValidator()

registerConfigValidator(namespace, validate): this

Defined in: packages/core/src/application/Application.ts:375

Attach a validator to a config namespace. Providers call this in onRegister(); the boot sequence runs every validator once — after providers register, before they boot. In a production-like deployment an error-level issue refuses boot; elsewhere issues are logged as warnings.

Parameters

namespace

string

validate

ConfigValidator

Returns

this

Example

// In a provider's onRegister():
this.app.registerConfigValidator("session", (value, { isProduction }) => {
  const cfg = value as SessionConfigShape | undefined;
  return isProduction && cfg?.secure !== true
    ? [{ level: "error", message: "session.secure must be true in production." }]
    : [];
});

useConfig()

useConfig(input): this

Defined in: packages/core/src/application/Application.ts:441

Pre-load config into the container before providers boot. Accepts a raw namespace map, the generated configs barrel, or a ConfigLoader from configLoader("./config").

If config was already provided via Application.create({ config }), this call is ignored (create() wins). That lets the framework-managed zerotal.ts always call useConfig(...) without conflicting when an app chose to pass config to create() instead.

Parameters

input

ConfigMap | ConfigLoader

Returns

this

Example

Application.create().useConfig(configLoader("./config")).register([...]);

withUserResolver()

withUserResolver(fn): this

Defined in: packages/core/src/application/Application.ts:773

Register the callback used to load an authenticated user from their session ID. Called by AuthMiddleware on every request that has a user_id in the session.

Parameters

fn

(id) => Promise<AuthenticatedUser | null>

Returns

this

Example

// bootstrap/app.ts
Application.create({ providers })
  .withUserResolver((id) => User.find(id));

withExceptionHandler()

withExceptionHandler(Handler): this

Defined in: packages/core/src/application/Application.ts:788

Register a custom exception handler for all unhandled route errors.

Parameters

Handler

() => ExceptionHandler

Returns

this

Example

// bootstrap/app.ts
import { Handler } from './app/exceptions/Handler.ts';
app.withExceptionHandler(Handler);

Container

container

readonly container: Container

Defined in: packages/core/src/application/Application.ts:297

The application's IoC Container. Providers bind services here in onRegister(), and code resolves them with container.make(token).


bind()

bind(callback): this

Defined in: packages/core/src/application/Application.ts:646

Register container bindings without authoring a ServiceProvider.

The callback receives the live Container and runs once during boot() — after the core singletons are registered and before any provider's onRegister(), so providers can still override these bindings. Ideal for app-level singletons, interface→implementation bindings, and contextual bindings that don't warrant a full provider.

Parameters

callback

(container) => void

Returns

this

Example

// bootstrap/app.ts
Application.create({ providers })
  .bind((c) => {
    c.singleton(Clock, () => new SystemClock());
    c.for(ReportService).give(Clock, () => new FixedClock());
  })
  .fileBasedRouting({ web: basePath("app/flow/pages") });

Environment

environment

Get Signature

get environment(): Environment

Defined in: packages/core/src/application/Application.ts:565

The runtime environment this application is running in.

Returns

Environment

Lifecycle

create()

static create(options?): Application

Defined in: packages/core/src/application/Application.ts:404

Create the process's application.

There is exactly one application per process. Calling create() a second time is an error — retrieve the existing app with currentApp, and call Application._resetInstance before creating another (tests). The environment defaults to APP_ENV.

Parameters

options?

CreateOptions = {}

{ providers?, env?, config? }.

Returns

Application

Throws

When an application already exists in this process.

Example

const app = Application.create({ providers: [AppServiceProvider], env: "web" });

booted

Get Signature

get booted(): boolean

Defined in: packages/core/src/application/Application.ts:547

Whether boot() has completed. Once booted the container is considered locked for app-level registration (see ContainerLockedError).

Returns

boolean


bootDurationMs

Get Signature

get bootDurationMs(): number | undefined

Defined in: packages/core/src/application/Application.ts:556

Wall-clock time the application took to boot, in milliseconds (undefined until booted).

Returns

number | undefined


boot()

boot(): Promise<void>

Defined in: packages/core/src/application/Application.ts:933

Run the boot sequence — phases 1–3: REGISTERING → BOOTING → BOOTED.

Returns

Promise<void>

Remarks

Discovers config and app/providers/*, resolves the provider dependency graph, runs every provider's onRegister/onBooting/onBooted, loads routes, and runs the convention loader. Idempotent — a second call after booting returns immediately. Called automatically by start and bootAsWorker if not already booted.

Throws

When a provider dependency cycle is detected, or in a production-like deployment when APP_KEY is too weak.


start()

start(port?): Promise<void>

Defined in: packages/core/src/application/Application.ts:1269

Boot (if needed) and start the HTTP server — phases 4–5: STARTING → STARTED.

Parameters

port?

number = 3000

TCP port to bind; pass 0 to let the OS choose a free port.

Returns

Promise<void>

Remarks

Boots first when not yet booted, runs providers' onStarting, binds Bun.serve() on port (registering the health endpoint, compiled routes, and any WebSocket handlers), then runs onStarted. Installs SIGTERM/SIGINT handlers that call stop and a SIGUSR2 handler that hot-reloads routes.

Example

const app = Application.create({ providers }).routing({ web: "./routes/web.ts" });
await app.start(3000);

bootAsWorker()

bootAsWorker(): Promise<void>

Defined in: packages/core/src/application/Application.ts:1430

Boot the application in the worker environment and block for queue/worker use.

Returns

Promise<void>

Remarks

Sets the environment to worker, boots if needed, and runs providers' onStarting/onStarted without binding an HTTP server. Installs SIGTERM/SIGINT handlers that drain providers (LIFO onStopping/onStopped) and then process.exit(0).


stop()

stop(options?): Promise<void>

Defined in: packages/core/src/application/Application.ts:1477

Phases 6–7: STOPPING → STOPPED (LIFO order).

By default the process exits once shutdown completes (CLI behaviour). Pass { exit: false } for embedded use or in-process test teardown, where the caller owns the process lifetime.

Parameters

options?
exit?

boolean

Returns

Promise<void>

Example

await app.stop();                 // CLI: stops providers, then process.exit(0)
await app.stop({ exit: false }); // embedded/tests: stops without exiting

close()

close(): Promise<void>

Defined in: packages/core/src/application/Application.ts:1504

Gracefully shut the application down without exiting the process.

The embedded/test-friendly counterpart to stop: it runs the same STOPPING → STOPPED teardown (server + providers, LIFO) but never calls process.exit, so the caller keeps ownership of the process lifetime. This is the method to reach for in in-process tests, REPLs, and when hosting the app inside a larger program. Equivalent to stop({ exit: false }).

Returns

Promise<void>

Example

const app = await Application.create().register([...]).start(0);
// …exercise the app…
await app.close(); // tears down, process stays alive

Providers

registerConcern()

registerConcern(descriptor): this

Defined in: packages/core/src/application/Application.ts:353

Register a convention descriptor for auto-discovery at boot. Providers call this in onRegister()/onBooting(); the loader runs all descriptors during the convention phase.

Parameters

descriptor

ConcernDescriptor

Returns

this


register()

register(providers): this

Defined in: packages/core/src/application/Application.ts:616

Register service providers to boot through the full lifecycle.

Idempotent by class identity: a provider already registered (here, in create(), discovered from app/providers/*, or pulled in via another provider's static dependsOn) is not registered again, so explicit and automatic registration can safely overlap. The first registration wins its position; ordering across dependencies is resolved at boot.

Parameters

providers

ProviderClass[]

Returns

this

Example

Application.create()
  .register([DatabaseProvider, AuthProvider])
  .register([AppServiceProvider]); // additive; duplicates are ignored

defer()

Call Signature

defer(token, Provider): this

Defined in: packages/core/src/application/Application.ts:667

Register one or more ServiceProviders as deferred — each boots only the first time one of its bindings is resolved.

Parameters
token

keyof ContainerBindings

Provider

ProviderClass

Returns

this

Example
// Single token:
app.defer('cache', CacheProvider);

// Object map:
app.defer({ cache: CacheProvider, mail: MailProvider });

// Array — each provider must declare static provides = ['token'] as const:
app.defer([CacheProvider, AuthProvider, QueueProvider]);

Call Signature

defer(map): this

Defined in: packages/core/src/application/Application.ts:668

Register one or more ServiceProviders as deferred — each boots only the first time one of its bindings is resolved.

Parameters
map

Partial<Record<keyof ContainerBindings, ProviderClass>>

Returns

this

Example
// Single token:
app.defer('cache', CacheProvider);

// Object map:
app.defer({ cache: CacheProvider, mail: MailProvider });

// Array — each provider must declare static provides = ['token'] as const:
app.defer([CacheProvider, AuthProvider, QueueProvider]);

Call Signature

defer(providers): this

Defined in: packages/core/src/application/Application.ts:669

Register one or more ServiceProviders as deferred — each boots only the first time one of its bindings is resolved.

Parameters
providers

DeferrableProviderClass[]

Returns

this

Example
// Single token:
app.defer('cache', CacheProvider);

// Object map:
app.defer({ cache: CacheProvider, mail: MailProvider });

// Array — each provider must declare static provides = ['token'] as const:
app.defer([CacheProvider, AuthProvider, QueueProvider]);

Routing

routing()

routing(config): this

Defined in: packages/core/src/application/Application.ts:485

Declare explicit route files for one or more named groups.

Each key is a group name; the value is the route file path (or an object with explicit prefix and middleware overrides).

Built-in defaults (no overrides required): "web" → prefix: "", middleware: ["web"] "api" → prefix: "/api", middleware: ["api"]

Custom group names must declare both prefix and middleware explicitly or an error is thrown at boot time.

Routes are loaded after all providers have finished onRegister(), so middleware groups are always available when route files run.

Three forms, and the call is additive — each invocation appends more groups, so you can chain calls to register several route sources:

.routing('./routes/web.ts') // bare file → "web" group .routing({ file: './routes/api.ts', prefix: '/api', middleware: ['api'] }) // single group .routing({ web: './routes/web.ts', api: './routes/api.ts' }) // named map

Parameters

config

string | RoutingConfig | RoutingEntry & { file: string; }

Returns

this

Example

// bootstrap/app.ts
Application.create({ providers })
  .routing({
    web: './routes/web.ts',
    api: './routes/api.ts',
  });

Throws

At call time when a custom (non web/api) group omits an explicit prefix or middleware.


fileBasedRouting()

fileBasedRouting(config): this

Defined in: packages/core/src/application/Application.ts:528

Declare file-based route directories for one or more named groups.

Same key semantics as routing() (built-in defaults for "web" / "api"). Each directory is scanned at boot time and every exported HTTP-method function is registered as a route.

Three forms, and the call is additive — each invocation appends more groups, so a tenant-scoped app can chain a plain surface group with a prefixed tenant group:

.fileBasedRouting(basePath('app/flow/pages/surface')) // bare dir → "web" group .fileBasedRouting({ // single group, explicit dir: basePath('app/flow/pages/[tenancy]'), prefix: '/:tenancy', middleware: [TenancyMiddleware], }) .fileBasedRouting({ web: './app/routes' }) // named map

Parameters

config

string | FileRoutingConfig | FileRoutingEntry & { dir: string; }

Returns

this

Example

// bootstrap/app.ts
Application.create({ providers })
  .fileBasedRouting(basePath('app/flow/pages/surface'))
  .fileBasedRouting({
    dir: basePath('app/flow/pages/[tenancy]'),
    prefix: '/:tenancy',
    middleware: [TenancyMiddleware],
  });

Throws

At call time when a custom (non web/api) group omits an explicit prefix or middleware.

Server

use()

use(middleware): this

Defined in: packages/core/src/application/Application.ts:702

Register one or more global middleware classes. Middleware runs in array order on every request.

Parameters

middleware

PipeClass | PipeClass[]

Returns

this

Example

app.use([CorsMiddleware.with({ origin: '*' }), InertiaMiddleware]);

globalMiddleware

Get Signature

get globalMiddleware(): PipeClass[]

Defined in: packages/core/src/application/Application.ts:758

Read-only copy of the resolved global middleware pipeline (kernel → provider auto → explicit .use(), in execution order).

Returns

PipeClass[]


enableDevWs()

enableDevWs(): this

Defined in: packages/core/src/application/Application.ts:816

Enable the /__dev/ws WebSocket HMR endpoint. Called by ServeCommand when running as --dev-worker.

Returns

this


serverMetrics()

serverMetrics(): object

Defined in: packages/core/src/application/Application.ts:832

Live server concurrency straight from Bun: HTTP requests currently being processed and open WebSocket connections. Zeroes when no server is bound (e.g. console/test runs). See https://bun.com/docs/runtime/http/metrics.

Returns

object

pendingRequests

pendingRequests: number

pendingWebSockets

pendingWebSockets: number


withWebSocket()

withWebSocket(handlers, upgradeData?, path?): this

Defined in: packages/core/src/application/Application.ts:848

Register WebSocket handlers so Bun.serve() enables the WS protocol. Multiple providers may register — each for its own path (e.g. /__flow/ws, /app/ws) — and connections are routed to the matching handler by request path. Omit path for a catch-all. Called by FlowProvider and BroadcastProvider before the server binds.

Parameters

handlers

WebSocketHandlers

upgradeData?

(req, server?) => Record<string, unknown>

path?

string

Returns

this