@glandjs/node@1.1.0-beta
@glandjs/node
The node:http transport — the reference implementation of the adapter contract, with no framework and no dependencies.
@glandjs/node binds your controllers and channels to node:http. It is the
reference implementation of the adapter contract: path normalisation, reply
coercion, cookie serialisation and the decision of when to end a socket are all defined
here, and the other four adapters are measured against it.
pnpm add @glandjs/core @glandjs/common @glandjs/events @glandjs/http @glandjs/node reflect-metadataNo HTTP server dependency. node:http is in the runtime.
Attaching it
Section titled “Attaching it”import { GlandFactory } from '@glandjs/core'import { Controller, Module } from '@glandjs/common'import { Get, Post } from '@glandjs/http'import { NodeBroker, type NodeContext } from '@glandjs/node'import { HttpStatus } from '@medishn/toolkit'
@Controller('/')export 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] })export 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()Exports
Section titled “Exports”| Symbol | Description |
|---|---|
NodeBroker<TEvents> |
The broker you pass to connectTo(). |
NodeContext<TEvents> |
The context your handlers receive, plus rawBody. |
NodeCore<TEvents> |
The application object. |
NodeAdapter<TEvents> |
The transport implementation. |
compile(pattern), matchSegments(pattern, actual) |
The path matcher, exported so you can unit-test your own patterns. |
collectBody(limit), parseBody(raw, contentType) |
The body reader, exported for the same reason. |
import { compile, matchSegments } from '@glandjs/node'
matchSegments(compile('/users/:id'), ['users', '42']) // { id: '42' }matchSegments(compile('/users/:id'), ['users', '42', 'x']) // undefinedA real onion, and native verbs
Section titled “A real onion, and native verbs”node:http has no opinions about hooks, so the adapter walks the middleware chain
itself as a recursive promise onion. await next() waits, and a downstream throw
reaches an upstream catch.
Every extended and WebDAV verb routes natively: the adapter compiles the pattern and
compares segments itself, and ALL matches any method.
http.use(async (ctx, next) => { const started = Date.now() await next() console.log(`${ctx.method} ${ctx.path} ${Date.now() - started}ms`)})Registration order is the tiebreak when two patterns match, and literal paths are compared case-sensitively.
One way to read a body
Section titled “One way to read a body”There is no framework parser to configure, so the adapter installs a single body
collector and decodes from the Content-Type:
http.bodyParser({ limit: '1mb' }) // 'b', 'kb', 'mb', 'gb'; defaults to 100 kBctx.body is the decoded value and ctx.rawBody is the original Buffer, so anything
unusual is still reachable. The collector is only installed when a parser was declared,
and it is always mounted first.
What it cannot do
Section titled “What it cannot do”useRaw()— there is nouse()innode:http, so the adapter warns and ignores it.useStaticAssets()queues a wildcard route instead.- Multipart — stream it yourself; the collector buffers.
trustProxy— nothing readsX-Forwarded-*, soctx.ipissocket.remoteAddress.- Views.
- Signed cookies —
{}.
What it does better than the rest: byte-range sendFile (Range is honoured and a
partial request answers 206), explicit Content-Length on files, and a
204/304 that never trips ERR_HTTP_BODY_NOT_ALLOWED.