Skip to content

FAQ

Common questions about Gland — stability, comparisons, testing, and the boundaries of the framework.

The core, the events package and the emitter are stable and used in production. The HTTP layer — @glandjs/http and @glandjs/express — is still at 1.0.0-beta, and a few features are incomplete. Check the Express caveats before depending on it.

If you need a proven HTTP stack today, run Gland channels beside Express and keep Express for routing. The two compose without either one knowing about the other.

NestJS is a larger, more prescriptive framework. Gland makes a different trade: a much smaller surface, an event-first request model instead of method injection, and a transport boundary that is an explicit seam rather than an implicit one. The cost is less built-in tooling. See Why Gland? for a side-by-side.

Yes, for controllers and channels. They are how the framework discovers your application — there is no naming convention and no config file. The underlying @glandjs/events and @glandjs/emitter packages have no decorators at all, and can be used standalone.

Can I use it with an existing Express app?

Section titled “Can I use it with an existing Express app?”

Yes, and it is a supported migration path. Attach a broker, register your Gland channels, and leave your existing routes in place:

const app = await GlandFactory.create(AppModule)
const server = app.connectTo(ExpressBroker)
server.use(existingMiddleware)
app.get('/legacy', legacyHandler) // the raw Express app

>= 20.9.0, with TypeScript >= 5.

In channels. A controller reads the request, calls or emits an event, and replies. If you find yourself writing a database query in a controller, that belongs in a channel.

Yes. ctx.call(event, payload, 'all') returns every handler’s result in registration order; without a strategy, the first result is returned. emit always runs every matching handler.

No. A controller can call any channel in the application, including one registered by a sibling module. Modules organise and bootstrap code — see Modules.

Because the channel registry is built once at startup, and event resolution is a constant-time lookup into a frozen registry. Per-request instances would have to be created during dispatch, which is the thing the design avoids. Use ctx.state for per-request data.

No. Only where the constructor parameter’s type is erased by TypeScript — interfaces, unions, primitives, symbols. A class parameter resolves from design:paramtypes on its own.

Wrap one link in forwardRef. The container throws a CircularDependencyError that prints the full cycle, and tells you to break it this way. See Dependency injection.

No. A channel is a plain class, so you can call its handler directly. A controller test asserts the events it emitted, using a small fake context. See Testing.

Any. Gland has no test dependencies of its own. The examples use Vitest, but Jest and node:test work equally well.

No. Dispatch is synchronous, in-process and at-most-once, by design. If a channel is not registered when an event is emitted, that event is lost. For durability, do the work in a channel that writes to a database or a message queue.

On the broker, yes. On @glandjs/emitter, no — by design, which is part of how it stays under 2 KB. Wrap on if you need it.

It declares both sides of a request/response event. IOEvent<Payload, Return> makes ctx.call() resolve to Return; a plain type means the return is void.

UnknownEventError carries an available array of every valid channel event name. Set GLAND_DEBUG=1 to see each channel binding logged at startup.

Usually not — database round trips and network latency dominate. See Performance for where the time actually goes, and for the few options that matter.

cacheSize if a small set of events dominates your traffic, and maxListeners if more than five handlers legitimately answer one event. Nothing else is worth touching.