Skip to content

Cell - Web Component ​

The HTTP layer of Cell: server, middleware chain, context, request body, cookies, sessions, CORS and route matching, on node:http with no third-party runtime dependency. Controllers come from @celljs/mvc; the guide is HTTP APIs.

Middleware ​

A middleware is a component bound to Middleware; every request passes through all of them in order of priority, highest first, before reaching a handler (MVC, RPC).

ts
import { Component } from '@celljs/core';
import { Context, HTTP_MIDDLEWARE_PRIORITY, Middleware } from '@celljs/web/lib/node';

@Component(Middleware)
export class TimingMiddleware implements Middleware {
    readonly priority = HTTP_MIDDLEWARE_PRIORITY - 10;

    async handle(ctx: Context, next: () => Promise<void>): Promise<void> {
        const start = Date.now();
        await next();
        ctx.response.setHeader('server-timing', `app;dur=${Date.now() - start}`);
    }
}

The built-in ones, highest first: trace (TRACE_MIDDLEWARE_PRIORITY, 2200), CORS (2100), body and HttpRequest (2000), cookies and health (1900), session (1800); @celljs/security adds its own below them. A middleware that throws hands the error to the error handlers.

Errors ​

Throw HttpError(status, message?, { cause?, expose? }) or NotFoundError(message) to answer with a status; anything else answers 500. Without a message, the message is the status text (Not Found). The stack, with each cause, is logged at the error level and never sent; a 4xx HttpError, or any error a handler answered with a redirect or a 4xx (login required, access denied), is an answer, not a failure, and is only logged at the debug level.

What the client sees is decided by ErrorMessageResolver: the message of an HttpError with expose (the default for 4xx); for anything else the message when web.errors.exposeMessages is true, else the status text (Internal Server Error). exposeMessages defaults to true outside production (NODE_ENV other than production), so messages of internal errors (SQL, hosts, file paths) stay in the log in production.

Error handlers are components bound to ErrorHandler with priority, canHandle(ctx, err) and handle(ctx, err): the handler with the highest priority that can handle the error answers, and when its handle throws the next one is tried; when none answers, the response ends with a bare 500, so no request hangs. ErrorResponseUtil.end(ctx, status, body, contentType?) ends a failed response (only ends it when the headers were sent). The built-in handlers have priorities up to 2000.

Request context ​

Context (@celljs/web/lib/node) is the current request wherever the code runs during it, carried across await with AsyncLocalStorage: Context.getRequest(), getResponse(), getCookies(), getSession(), getTraceId(), and attributes with Context.setAttr(key, value) / Context.getAttr(key) (request scope by default, AttributeScope.App for the application).

Trace ids and health ​

Every response carries the trace id in X-Cell-Trace-ID (web.trace.responseField; requestField names the request header it can come from). GET /health (web.health.url, web.health.method) answers the aggregated status of the HealthIndicator components as JSON.

Sessions (web.session) ​

The session is stored in a signed cookie (CookieSessionStore) and saved after the request when it changed (autoCommit). maxAge defaults to one day. Read it with Context.getSession() or @Session() in a controller; bind your own SessionStore to keep sessions elsewhere.

Requests and responses ​

Request is Node's IncomingMessage with path (no query, not decoded), query (with brackets, as express parsed it: ?a[b]=1&c=2&c=3 → { a: { b: '1' }, c: ['2', '3'] }), protocol / secure (from the connection; X-Forwarded-Proto is not read), hostname, ip, originalUrl, body and get(header). Response is Node's ServerResponse plus body, which the http middleware writes after the handler; there is no res.status() / res.send(): set statusCode and body, or call end().

HttpServerUtil.createHandler(container) is the (req, res) handler of the application and HttpServerUtil.createHttpServer(handler) a server whose requests are HttpRequests; requests of another server (a dev server, a test, a serverless proxy) get the members with HttpRequest.enhance(req). Express middleware still mounts with convertExpressMiddleware() when it only needs Node's req / res.

Parameter types ​

TypeConversionPipe converts request data to String, Number, Boolean and Date parameters as class-transformer did ('5' → 5). Parameters of other classes get the data as it is; add @celljs/validation to turn it into instances and validate them with class-validator.

Request body (web.body) ​

request.body is JSON (application/json), text (text/plain), a Buffer (application/octet-stream) or a form (application/x-www-form-urlencoded, with brackets: a[b]=1 → { a: { b: '1' } }); any other request gets {}. gzip, deflate and br bodies are decompressed.

KeyDefault
limit100kbLargest body after decompression, in bytes or as '5mb'
inflatetruefalse refuses compressed bodies
stricttrueJSON must be an object or an array
parameterLimit1000Most parameters in a form
depth32Most levels of brackets in a form

A malformed body answers 400, a large one 413, an unsupported charset or encoding 415.

Cookies (web.cookies) ​

With keys, cookies are signed (a <name>.sig cookie, HMAC-SHA1 with the first key, the format of the cookies/keygrip packages, so existing signed cookies stay valid); any of the keys verifies, so keys can be rotated by putting the new one first. Cookies.get(name, { signed: true }) checks the signature; get(name) without options does not.

The default keys are a placeholder (['abcdef']): set your own in production, for example keys: ["${env.COOKIE_KEY}"], since they also sign the session cookie. CookiesSecurityCheck reports the placeholder (CELL_SECURITY_DEFAULT_COOKIE_KEYS), no keys (CELL_SECURITY_COOKIE_KEYS_MISSING: the session cookie can then be edited by the client) and keys shorter than 16 characters (CELL_SECURITY_WEAK_COOKIE_KEYS) to cell check and to a production start (see securityChecks in @celljs/core).

CORS (web.cors) ​

The options of the express cors package: origin (default '*'; a string, true for the request's origin, a RegExp or a list, false for none), methods, allowedHeaders, exposedHeaders, credentials, maxAge, preflightContinue, optionsSuccessStatus.

Route patterns ​

RequestMatcher.match(pattern) takes a PathPattern: :name is one segment, * is anything (into _), (...) is optional, \\ makes the next character plain. /users/:id on /users/42 gives { id: '42' }; no match gives null.