@glandjs/node@1.1.0-beta
@glandjs/node
The node:http transport: no framework, no dependencies.
# pnpm pnpm add @glandjs/node# npm npm install @glandjs/node# yarn yarn add @glandjs/node# bun bun add @glandjs/node| Package | @glandjs/node |
| Version | 1.1.0-beta |
| Repository | glandjs/http |
| License | MIT |
| Node | >= 20 |
| Dependencies | @glandjs/core ^1.0.3-beta · @glandjs/events ^1.1.0 · @medishn/toolkit ^1.0.4 · tslib ^2.8.1 |
| Peer dependencies | @glandjs/common ^1.0.3-beta · reflect-metadata ^0.2.2 |
Description
Section titled “Description”@glandjs/node serves a Gland application over Node’s built-in HTTP module. There
is no Express, no Fastify, no Koa, no Hono — and no dependency that exists only to
route a request. The router, the reply writer, the body collector and the cookie
serialiser are all in this package, in two files.
It is here for three reasons, in order of importance:
- It proves the contract. If
@glandjs/httpcan serve HTTP with no framework at all, then the four framework adapters are genuinely thin — and a sixth adapter is a mechanical job rather than a research project. - It is the smallest install. For a service that is one endpoint and a health check, a framework is a large cost for a small problem. normalisation, reply coercion, cookie serialisation, when to end a socket — is implemented here against primitives, so there is exactly one place to look up what the intended behaviour actually is.
Install
Section titled “Install”npm install @glandjs/core @glandjs/common @glandjs/http @glandjs/node reflect-metadataimport { GlandFactory } from '@glandjs/core';import { Controller, Module, On, Channel } from '@glandjs/common';import { Get, Post, HttpReply } from '@glandjs/http';import { NodeBroker, type NodeContext } from '@glandjs/node';import { HttpStatus } from '@medishn/toolkit';
@Controller('/')class HelloController { @Get('/') index() { return { framework: 'node:http' }; }
@Get('/hello/:name') hello(ctx: NodeContext) { return { message: `Hello, ${ctx.params.name}!` }; }
@Post('/echo') echo(ctx: NodeContext) { return ctx.body; }
@Get('/gone') gone(ctx: NodeContext) { return ctx.throw(HttpStatus.GONE, { detail: 'removed' }); }}
@Module({ controllers: [HelloController] })class AppModule {}
const { app } = await GlandFactory.create(AppModule);const http = app.connectTo(NodeBroker, { prefix: '/api' });
http.listen(3000, { host: '0.0.0.0' });await http.ready();curl localhost:3000/api/hello/worldThe pipeline
Section titled “The pipeline”node:http has no middleware concept, so this adapter walks the recorded queue
itself. That makes it the one adapter where the onion is unambiguously real:
X-Powered-By ──▶ body collector ──▶ your middleware ──▶ route handler ──▶ 404- The chain runs for every request, including one that matched nothing. A
401middleware protects a 404 as much as it protects a handler, which is the behaviour you want and the one a framework adapter has to work to preserve. await next()awaits the rest of the chain. Athrowunwinds through every frame, with the error rendered at the outermost one.- The body collector is first, ahead of anything you add.
ctx.bodyis parsed from the bytes it accumulates, so a middleware mounted before it would seeundefinedon every request. - Registration order is the tiebreak. A specific route registered before a wildcard wins — the same rule the other four adapters inherit from their frameworks, and the one an application migrating from Express expects.
The router
Section titled “The router”A compiled segment matcher, not a regular expression. Two reasons:
:idmust not match across a/, so/a/bis not/a/:x/y;- interpolating an unescaped user value into a
RegExpis not a risk worth taking when the alternative is thirty lines.
Paths are normalised first — one leading slash, no trailing slash, no empty
segments — and matched case-sensitively, per RFC 3986. A case-insensitive
match is how /Users and /users quietly become one route.
import { compile, matchSegments } from '@glandjs/node';
matchSegments(compile('/users/:id'), ['users', '42']); // { id: '42' }matchSegments(compile('/users/:id'), ['users', '42', 'x']); // undefinedWhat this adapter does not do
Section titled “What this adapter does not do”| Missing | Why, and what to use instead |
|---|---|
useRaw() |
There is no framework-native middleware to hand a raw request to. It warns and is ignored. |
| Per-format parsers | There is one way to read a body, and the decoding follows Content-Type. ctx.rawBody holds the raw Buffer for anything unusual. |
x-powered-by, trust proxy, templating |
Set X-Powered-By with poweredBy; nothing here reads X-Forwarded-* or renders a view. |
The body collector is only installed when a parser was declared, matching the documented default:
app.connectTo(NodeBroker, { bodyParser: { limit: '2mb' } }); // collects and parsesapp.connectTo(NodeBroker); // `ctx.body` stays undefineduseStaticAssets() does work: it queues a wildcard route, which is all
serveStatic needs.
| Export | Kind |
|---|---|
NodeBroker / NodeBrokerClass |
What app.connectTo() takes |
NodeCore |
The application |
NodeAdapter |
The adapter |
NodeContext |
The context |
collectBody |
The body parser, mounted for you |
compile, matchSegments |
The router, for inspection and testing |
Documentation
Section titled “Documentation”License
Section titled “License”MIT