Skip to content

XenoApplication

XenoApplication is the runtime API used after the application has been composed. XenoAppBuilder.build() initializes the queued modules and returns the configured application. Unless a custom application factory is configured, the builder returns an XenoApplication backed by its service container.

The builder and the application have different responsibilities:

Component Responsibility
XenoAppBuilder Configures and initializes the application modules.
XenoApplication Exposes services and operations used at runtime.

Use the App Builder to configure the application. Use the returned application to access registered services, run scoped work, dispatch requests, or start the configured HTTP server.

The runtime application is obtained after composition:

XenoAppBuilder
│
├── Configure services and integrations
├── Queue application modules
▼
build()
│
├── Initialize modules
▼
XenoApplication
│
├── Access services
├── Run scoped operations
└── Handle requests or listen

build() is asynchronous. Once it succeeds, use its return value as the application runtime. It does not return the Service Container.

The main.ts example obtains the application from its bootstrap function, then resolves services and start the server:

import 'dotenv/config'
import { bootstrap } from './bootstrap'
async function main() {
const app = await bootstrap()
const { logger, configurationService } = app.getServices()
logger.info('Initializing app...')
const port = configurationService.getNumber('PORT', 3000)
try {
await app.listen({
port,
host: 'localhost',
onListening: (address) => {
logger.info(
`Xeno Node server is running on http://${address.host}:${address.port}`,
)
},
})
} catch {
process.exit(1)
}
}
main()

The bootstrap function’s implementation is application-specific; this example uses only the contract visible at the call site. listen() returns a promise, so awaiting it lets the try/catch handle a rejected server startup.

Call getServices() to access services resolved from the application’s root container:

const { logger, configurationService } = app.getServices()

The returned object is a typed proxy: reading a property resolves the corresponding registry key from the container. Root access is appropriate for services that can be resolved outside a scope. A scoped service must instead be resolved within an active service scope.

See Dependency Injection for registration and lifetime details.

When a request or other execution has already established a scope, call getScopedServices():

const { logger } = app.getScopedServices()
logger.info('Operation in active scope')

The proxy resolves service keys through the active scope. If no scope is active, getScopedServices() throws an error. For work that needs its own scope, use runInScope() instead.

runInScope() creates a request context and service scope for an operation. Its callback receives a proxy that resolves services through that scope:

await app.runInScope(async (services) => {
services.logger.info('Run operation in scope')
})

The callback must return a promise. An optional second argument can provide partial request context, such as identity, network, or tracing information.

The context execution owns the scope lifecycle. The scope is disposed when the operation completes, including when the callback fails. See Context and Scopes for more about scoped service access.

handle() dispatches an operation for an incoming Xeno request:

Its generic contract accepts an XenoIncomingRequest, a token constrained to keyof TRegistry, and optional input; it returns a Promise<ResponseDto<TOutput>>.

The token identifies the controller operation in the application registry; input is optional. The method adapts the incoming request and delegates to the HTTP dispatcher, returning a ResponseDto in a promise.

Use this method only when the required HTTP adapter, dispatcher, and middleware services are registered. For a network-facing server, configure the selected HTTP integration and use listen().

listen() resolves the registered HTTP server, creates its options, and awaits the server’s listen operation:

await app.listen({
port: 3000,
host: 'localhost',
})

The HTTP server must be configured during application composition. The NativeListenOptions accepted by the method are passed through the server options factory; transport-specific setup belongs to the selected integration.

Need API
Resolve a root service getServices()
Resolve a service in an existing scope getScopedServices()
Create a scope for an operation runInScope()
Dispatch an incoming request to an operation handle()
Start the configured HTTP server listen()

Do not treat XenoApplication as the composition root. Configure dependencies and integrations with XenoAppBuilder, then use the returned application for runtime work.

XenoApplication is the default runtime object returned by XenoAppBuilder.build(). It provides distinct APIs for root service resolution, scoped service resolution, scoped operations, request dispatch, and server startup. Choose the API that matches the execution boundary so scoped services remain inside an active scope.


Xeno.JS is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you’d like to join them, please read more at support section