Headers
HeadersSubset is the type for the headers a handler reads or sends. mion validates them like any other param.
Reading Request Headers
mion.headersMiddleware creates a middleware that reads request headers. Its second parameter is a HeadersSubset<Required, Optional>: the first type lists the headers a request must send, the second those it may send.
import {createMionRouter} from '@mionjs/router';
import {HeadersSubset} from '@mionjs/core';
const mion = createMionRouter();
const traceWithOptionalAgent = mion.headersMiddleware(
async (
ctx,
{headers}: HeadersSubset<'X-Trace-Id', 'User-Agent'>
): Promise<void> => {
const traceId = headers['X-Trace-Id']; // always present
const userAgent = headers['User-Agent']; // may be undefined
console.log(`Trace: ${traceId}, Agent: ${userAgent ?? 'unknown'}`);
}
);
export {traceWithOptionalAgent};
| Rule | What it means |
|---|---|
| Name case | Not case sensitive: a request with authorization matches Authorization. You read the value by the name you declared. |
| Values | Always strings. Parse numbers or dates yourself. |
| Other params | Params after the HeadersSubset come from the request body, as in any middleware. |
ctx.request.headers.get(name), see Call Context.Missing or Invalid Headers
A request without a required header, or with an empty one, ends with a validation-error (HTTP 422) before the handler runs. The client gets it typed like any validation error.
Sending Response Headers
Return a HeadersSubset from a middleware or a route to send its values as response headers, never in the body. In code built with mion, new HeadersSubset(...) throws a headers-validation-error inside your handler when a header is missing or has the wrong type, and no header is sent. The router does not check headers after your handler returns.
import {HeadersSubset} from '@mionjs/core';
import {createMionRouter, Routes} from '@mionjs/router';
const mion = createMionRouter();
const routes = {
// sets the header on the response of every route after it
noCache: mion.middleware(
(ctx): HeadersSubset<'Cache-Control'> =>
new HeadersSubset({'Cache-Control': 'no-store'})
),
// reads a request header and returns a response header
locale: mion.headersMiddleware(
(
ctx,
{headers}: HeadersSubset<never, 'Accept-Language'>
): HeadersSubset<'Content-Language'> =>
new HeadersSubset({
'Content-Language': headers['Accept-Language'] ?? 'en',
})
),
} satisfies Routes;
Building Headers in a Generic Function
Inside a generic function mion does not know the header names, so the build stops with marker-in-generic-function. Pass undefined as the second argument to skip the check.
import {HeadersSubset} from '@mionjs/core';
function withHeader<Name extends string>(
headers: Record<Name, string>
): HeadersSubset<Name> {
return new HeadersSubset<Name>(headers, undefined);
}
export {withHeader};
Headers on Every Response
The responseHeaders router option adds headers to every response, on every platform. When two sources set the same header, the one higher in the table wins.
import {createMionRouter} from '@mionjs/router';
// also sent on error responses
createMionRouter({responseHeaders: {'x-api': 'store'}});
| Order | Source |
|---|---|
| 1 | A handler: a returned HeadersSubset or ctx.response.headers |
| 2 | The router's responseHeaders |
| 3 | server: mionjs, added by mion |
content-type: application/json; charset=utf-8. Neither a handler nor responseHeaders can change it.Build Version Header
Only routes that run the mionFetchMetadata middleware send an x-build-version header: the version of the API the server was built from. A client built against another version gets an api-version-mismatch error, see API Version Check. Set apiVersionCheck: false to stop sending the header and turn the client check off.
A version set in responseHeaders replaces it on every response.
const mion = createMionRouter({apiVersionCheck: false});
Error Type Header
When an error ends the request, the response gets an x-rpc-error header with the error's type. A proxy or a log can read it without parsing the body.
type with characters other than letters, digits and _ . : @ -, or longer than 128 characters, makes the header read unknown-error. The body still has the full type.Sending Headers from the Client
On the client, send a headers middleware's HeadersSubset from its onRequest hook. For fixed headers on every request, use fetchOptions.headers in initClient.
import {HeadersSubset} from '@mionjs/core';
import {initClient} from '@mionjs/client';
import type {HeadersApi} from '../router/headers-api.routes.ts';
const {routes, middlewares} = initClient<HeadersApi>({
baseURL: 'http://localhost:3000',
fetchOptions: {headers: {'Accept-Language': 'en'}},
});
// the HeadersSubset is sent as HTTP headers, not in the body
middlewares.trace.onRequest((trace) =>
trace(new HeadersSubset({'X-Trace-Id': crypto.randomUUID()}))
);
await routes.getDownloadUrl('report-42').call();
import {HeadersSubset} from '@mionjs/core';
import {createMionRouter, Routes} from '@mionjs/router';
const mion = createMionRouter();
const routes = {
trace: mion.headersMiddleware(
(ctx, {headers}: HeadersSubset<'X-Trace-Id'>): void => {
console.log('trace', headers['X-Trace-Id']);
}
),
// returns a header, not a body value
getDownloadUrl: mion.route(
(ctx, fileId: string): HeadersSubset<'x-download-url'> =>
new HeadersSubset({'x-download-url': `/files/${fileId}`})
),
} satisfies Routes;
const api = mion.initRoutes(routes);
export type HeadersApi = typeof api;
HeadersSubset value wins. More on hooks in Middleware Hooks.Reading Response Headers on the Client
When a route returns a HeadersSubset, call() returns a HeadersSubset built from the response headers. The client reads only the names the return type declares.
import {initClient} from '@mionjs/client';
import type {HeadersApi} from '../router/headers-api.routes.ts';
const {routes} = initClient<HeadersApi>({baseURL: 'http://localhost:3000'});
const [download] = await routes.getDownloadUrl('report-42').call();
if (download) console.log(download.headers['x-download-url']);
import {HeadersSubset} from '@mionjs/core';
import {createMionRouter, Routes} from '@mionjs/router';
const mion = createMionRouter();
const routes = {
trace: mion.headersMiddleware(
(ctx, {headers}: HeadersSubset<'X-Trace-Id'>): void => {
console.log('trace', headers['X-Trace-Id']);
}
),
// returns a header, not a body value
getDownloadUrl: mion.route(
(ctx, fileId: string): HeadersSubset<'x-download-url'> =>
new HeadersSubset({'x-download-url': `/files/${fileId}`})
),
} satisfies Routes;
const api = mion.initRoutes(routes);
export type HeadersApi = typeof api;
x-build-version included. List the ones the client reads in Access-Control-Expose-Headers, for example with responseHeaders.