Documentation / @zerotal/core / index / Application
Class: Application
Defined in: 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: 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
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: 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
Returns
this
Example
Application.create().useConfig(configLoader("./config")).register([...]);
withUserResolver()
withUserResolver(
fn):this
Defined in: 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: 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
readonlycontainer:Container
Defined in: 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: 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: application/Application.ts:565
The runtime environment this application is running in.
Returns
Environment
Lifecycle
create()
staticcreate(options?):Application
Defined in: 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: 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: 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: 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: 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: 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: 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: 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: 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
Returns
this
register()
register(
providers):this
Defined in: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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
upgradeData?
(req, server?) => Record<string, unknown>
path?
string
Returns
this