@glandjs/http@1.0.0-beta
@glandjs/http
The HTTP layer — route decorators, HttpContext, the HttpCore application surface, HttpBroker and the server factory.
@glandjs/http contains everything HTTP that is not specific to Express: the route
decorators, the abstract context, the protocol-agnostic application object, and the
broker that turns discovered routes into real ones. A concrete adapter such as
@glandjs/express supplies the transport.
pnpm add @glandjs/httpWhy this layer exists
Section titled “Why this layer exists”HTTP is not your app — it’s just a protocol. Gland treats it that way.
@glandjs/http creates an isolated, internal event channel named http which routes
messages from incoming HTTP requests into your Gland modules and controllers — and
then emits outgoing responses back through the same channel.
The result is a clean separation of concerns: your core business logic doesn’t know or care where a request came from or how the response is sent. It only handles events.
This package implements no HTTP server itself. It defines the interface — a
protocol contract — and relies on adapter packages such as
@glandjs/express to handle the actual server lifecycle. That is
what lets you plug in a different HTTP engine later without touching your application.
The HTTP layer is designed to be replaceable. Every adapter connected to it becomes a plug-in that speaks the same language — message-driven, modular, and completely testable.
Route decorators
Section titled “Route decorators”Ten decorators, all taking an optional path. The default path is '/'.
import { Get, Post, Put, Patch, Delete, Options, Head, Search, All, RequestMapping, RequestMethod,} from '@glandjs/http'| Decorator | Method |
|---|---|
@Get(path?) |
GET |
@Post(path?) |
POST |
@Put(path?) |
PUT |
@Delete(path?) |
DELETE |
@Patch(path?) |
PATCH |
@Options(path?) |
OPTIONS |
@Head(path?) |
HEAD |
@Search(path?) |
SEARCH |
@All(path?) |
ALL |
@RequestMapping({ path?, method? }) |
any RequestMethod |
They write PATH_METADATA and METHOD_METADATA onto the method function itself,
which is exactly what core’s Explorer reads.
@Controller('products')export class ProductController { @Get() list(ctx) {} @Get(':id') getOne(ctx) {} @Post() create(ctx) {}
@RequestMapping({ path: 'search', method: RequestMethod.GET }) search(ctx) {}}RequestMethod
Section titled “RequestMethod”enum RequestMethod { GET = 'GET', POST = 'POST', PUT = 'PUT', DELETE = 'DELETE', PATCH = 'PATCH', ALL = 'ALL', OPTIONS = 'OPTIONS', HEAD = 'HEAD', SEARCH = 'SEARCH', PROPFIND = 'PROPFIND', PROPPATCH = 'PROPPATCH', MKCOL = 'MKCOL', COPY = 'COPY', MOVE = 'MOVE', LOCK = 'LOCK', UNLOCK = 'UNLOCK',}HttpContext
Section titled “HttpContext”The abstract context. Every adapter provides a concrete subclass.
abstract class HttpContext<TRequest = any, TResponse = any, TEvents extends EventRecord = EventRecord> extends Context<TEvents> { constructor( protected readonly events: HttpEventCore<TEvents>, public readonly req: TRequest, public readonly res: TResponse, )
params: Record<string, string> host?: string
// Request get body(): any get query(): Record<string, string | string[] | undefined> get headers(): Record<string, string | string[]> get method(): RequestMethod get path(): string get url(): string get originalUrl(): string get hostname(): string get ip(): string | undefined get protocol(): string get secure(): boolean get xhr(): boolean get stale(): boolean get fresh(): boolean get subdomains(): string[] get cookies(): Record<string, string> get signedCookies(): Record<string, string> accepts(types?): string | false is(type): string | false | null
// Response status(code): this send(data, statusCode?, headers?): this json(data, statusCode?, headers?): this html(html, statusCode?, headers?): this text(text, statusCode?, headers?): this xml(xml, statusCode?, headers?): this jsonp(data): this redirect(url): this location(url): this end(): this attachment(filename?): this vary(field): this render(view, options?, callback?): this sendFile(path, options?, callback?): this download(path, filename, options?): this format(formatters): this sse(): this
// Headers setHeader(name, value): this setHeaders(headers): this getHeader(name): string | string[] | undefined hasHeader(name): boolean removeHeader(name): this
// Cookies setCookie(name, value, options?): this getCookie(name): string | undefined clearCookie(name, options?): this deleteCookie(name, options?): void
throw(status: HttpStatus, options?): this}Inherited from Context: state, setState(), error, emit(), call(), on(),
once(), off().
HttpCore
Section titled “HttpCore”The application object you get back from connectTo(). It mirrors the shape of a
familiar web framework’s app instance.
class HttpCore<TServer, TApp, TRequest, TResponse> { constructor(adapter: HttpServerAdapter<...>, options?: HttpApplicationOptions)
get broker(): HttpEventCore<HttpGlandEvents> get id(): string
listen(port: number, args?: { host?: string; message?: string }): void use(...args: any[]): void
get(path, action): this post(path, action): this put(path, action): this patch(path, action): this delete(path, action): this head(path, action): this options(path, action): this all(path, action): this search(path, action): this propfind(path, action): this proppatch(path, action): this mkcol(path, action): this copy(path, action): this move(path, action): this lock(path, action): this unlock(path, action): this
json(options?): this urlencoded(options?: { extended?: boolean }): this raw(options?): this text(options?): this
enableCors(options: CorsConfig): void setGlobalPrefix(prefix: string): void useStaticAssets(path: string): void setViewEngine(engine: string): this setBaseViewsDir(path: string | string[]): this handleError(error: unknown, message: string): void}const server = app.connectTo(ExpressBroker)
server.use((ctx, next) => { ctx.state.t0 = Date.now(); return next() })server.useStaticAssets('public')server.setGlobalPrefix('/api')server.enableCors({ origin: '*' })server.json()server.listen(3000, { host: '0.0.0.0', message: 'ready' })HttpBroker
Section titled “HttpBroker”Bridges core’s route events to the application object.
class HttpBroker<TEvents, TApp, TOptions> extends BrokerAdapter<TEvents, TApp, TOptions> { constructor(instance: TApp, options?: TOptions) initialize(): TApp}initialize() subscribes to gland:define:route and calls
app[payload.method.toLowerCase()](payload.meta.path, payload.action) for each
discovered route. Concrete adapters extend it and supply the instance:
export class ExpressBroker<TEvents extends EventRecord> extends HttpBroker< TEvents, ExpressCore<TEvents>, HttpApplicationOptions> { constructor(options?: HttpApplicationOptions) { super(new ExpressCore<TEvents>(options), options) }}HttpServerAdapter
Section titled “HttpServerAdapter”The abstract adapter every transport implements.
abstract class HttpServerAdapter<TServer, TApp, TRequest, TResponse> { constructor(public instance: TApp) events: HttpEventCore<HttpGlandEvents>
abstract initialize(): Promise<void> | void abstract listen(port: string | number, hostname?: string, message?: string): void abstract use(...args: any): any abstract registerRoute(method: string, path: string, action: RouteAction): void abstract enableCors(options: CorsConfig): void abstract setViewEngine(engine: string): this abstract setBaseViewsDir(path: string | string[]): this abstract setGlobalPrefix(prefix: string): void abstract useStaticAssets(path: string, options?: any): void abstract json(options?: any): this abstract urlencoded(options?: { extended?: boolean }): this abstract raw(options?: any): this abstract text(options?: any): this
close(): Promise<void> handleError(error: any, message: string): void // emits 'server:crashed', then rethrows}HttpEventCore
Section titled “HttpEventCore”An EventBroker with one addition.
class HttpEventCore<TEvents extends EventRecord> extends EventBroker<TEvents> { safeEmit<K>(event: K, payload: TEvents[K]): boolean}safeEmit returns false when nothing was listening, instead of throwing — used for
internal events such as server:crashed where “nobody cares” is not an error.
ServerFactory
Section titled “ServerFactory”class ServerFactory { static create(options?: HttpApplicationOptions, listener?: RequestListener): Server}Returns an http or https server depending on options.https.
Options
Section titled “Options”interface HttpApplicationOptions { https?: HttpsOptions}
interface HttpsOptions { key?: any; cert?: any; ca?: any; pfx?: any; passphrase?: string ciphers?: string; honorCipherOrder?: boolean requestCert?: boolean; rejectUnauthorized?: boolean SNICallback?: (servername: string, cb: (err: Error, ctx: any) => any) => any secureOptions?: number // …}const server = app.connectTo(ExpressBroker, { https: { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') },})interface CorsOptions { origin?: StaticOrigin | CustomOrigin // @default '*' methods?: string | string[] // @default 'GET,HEAD,PUT,PATCH,POST,DELETE' allowedHeaders?: string | string[] exposedHeaders?: string | string[] credentials?: boolean // @default false maxAge?: number preflightContinue?: boolean // @default false optionsSuccessStatus?: number // @default 204}
type CorsConfig = boolean | CorsOptions | CorsOptionsDelegate<IncomingMessage>pnpm add corsCookies and options
Section titled “Cookies and options”interface CookieOptions { maxAge?: number; name?: string; secret?: string path?: string; domain?: string sameSite?: 'strict' | 'lax' | 'none' | boolean secure?: boolean; secureProxy?: boolean; httpOnly?: boolean signed?: boolean; overwrite?: boolean; expires?: Date}interface SendOptions { acceptRanges?: boolean; cacheControl?: boolean dotfiles?: 'allow' | 'deny' | 'ignore' end?: number; etag?: boolean extensions?: string[] | string | boolean immutable?: boolean index?: string[] | string | boolean lastModified?: boolean; maxAge?: string | number root?: string; start?: number}RouteAction and the CORS types are not re-exported from the package root. Import
them from the subpath:
import type { RouteAction, CorsConfig, StaticOrigin } from '@glandjs/http/types'type RouteAction<TRequest, TResponse> = (ctx: HttpContext<TRequest, TResponse>) => any | Promise<any>Framework events
Section titled “Framework events”interface HttpGlandEvents { options?: HttpApplicationOptions 'server:crashed': { message: string; error: any; stack: any }}Subscribe to server:crashed to log transport failures centrally:
server.broker.on('server:crashed', ({ message, error }) => { logger.error({ message, error }, 'http server error')})