@glandjs/koa@1.1.0-beta
@glandjs/koa
The Koa 3 transport.
# pnpm pnpm add @glandjs/koa# npm npm install @glandjs/koa# yarn yarn add @glandjs/koa# bun bun add @glandjs/koa| Package | @glandjs/koa |
| Version | 1.1.0-beta |
| Repository | glandjs/http |
| License | MIT |
| Node | >= 20 |
| Dependencies | @glandjs/core ^1.0.3-beta · @glandjs/events ^1.1.0 · @koa/router ^13.1.0 · @medishn/toolkit ^1.0.4 · koa ^3.0.0 · tslib ^2.8.1 |
| Peer dependencies | @glandjs/common ^1.0.3-beta · koa-bodyparser ^4.4.1 · koa-static ^5.0.0 · reflect-metadata ^0.2.2 |
Description
Section titled “Description”@glandjs/koa is the Koa adapter for Gland’s HTTP layer.
Koa has the closest middleware model to Gland’s — so close that the adapter is
the thinnest here, and await next() genuinely waits for the rest of the chain.
A downstream throw reaches an upstream catch.
Install
Section titled “Install”npm install @glandjs/core @glandjs/common @glandjs/http @glandjs/koa reflect-metadatakoa-static (static assets).
import { GlandFactory } from '@glandjs/core';import { KoaBroker, type KoaContext } from '@glandjs/koa';import { Get } from '@glandjs/http';import { Controller, Module } from '@glandjs/common';
@Controller('/products')class ProductController { @Get('/:id') async find(ctx: KoaContext) { return ctx.call('db:product:find', ctx.params.id); }}
@Module({ controllers: [ProductController] })class AppModule {}
const { app, shutdown } = await GlandFactory.create(AppModule);const http = app.connectTo(KoaBroker, { poweredBy: false });
http.bodyParser({ limit: 1_000_000 });http.use(async (ctx, next) => { const started = Date.now(); await next(); // really waits console.log(`${ctx.method} ${ctx.path} ${Date.now() - started}ms`);});http.useRaw(require('koa-static')('./public'));
http.listen(3000);The middleware onion
Section titled “The middleware onion”Koa is the model this layer was written against, so the bridge is a one-liner:
app.use(async (ctx, next) => { try { await next(); } catch (error) { ctx.status(503).json({ error: 'upstream unavailable' }); }});A downstream throw propagates through next() and reaches the catch — the
full onion, with no caveats.
The prefix is applied once
Section titled “The prefix is applied once”Paths arrive at the router already prefixed, because
HttpServerAdapter.route() applies it. The adapter therefore mounts
app.use(router.routes()) with no router prefix.
A Router mounted at /api with routes registered at /api/products serves
/api/api/products, and 404s everything the application exists to answer. That
is the single most common way a prefix goes wrong.
Body parsing
Section titled “Body parsing”koa-bodyparser is loaded on request rather than bundled. Parsing is a security
decision — limits, encodings, content types — and Koa’s answer to it is a
well-known package rather than a rewrite.
app.bodyParser({ limit: 1_000_000 }); // 100 kB by defaultMultipart is not handled here: streaming a file upload through a buffering parser
is how a 10 MB video becomes a 10 MB heap spike. Use @koa/multer through
useRaw.
poweredBy: falseis a no-op. Koa sets noX-Powered-By; that is Express. A reverse proxy that adds one is the proxy’s configuration.- Signed cookies work if a signed-cookie middleware is mounted, and
ctx.signedCookiesis{}without one. - The 404 is mounted after the router, and renders the same problem document as every other adapter rather than Koa’s own body.
| Export | Kind |
|---|---|
KoaBroker / KoaBrokerClass |
What app.connectTo() takes |
KoaCore |
The application, with a typed koa getter |
KoaAdapter |
The adapter |
KoaContext, KoaState, GlandKoaContext |
The context |
The full surface is in the API reference.
Documentation
Section titled “Documentation”License
Section titled “License”MIT