Skip to main content
Every controller receives a context, and the incoming HTTP request lives on context.request. It is an HttpRequest instance (implementing the IRequest interface) that wraps the native Request and exposes the parsed pieces you actually need: the URL, the method, the headers, the query string, the route params, the decoded body, the client IP, the detected locale, and any uploaded files. The framework builds it for you from the native request before the controller runs, so you never construct one by hand — you read it. For the values you reach for most, the context also carries convenience accessors that point straight at the request: context.params, context.queries, and context.payload are the same objects as context.request.params, context.request.queries, and context.request.payload.

Why this object

  • One parsed surface. The native Request gives you a raw URL string and a Headers bag. IRequest hands you a parsed IUrl, a typed queries record, route params, and the decoded payload — no re-parsing in every controller.
  • Typed by config. IRequest<Config> is generic over params, payload, and queries, so a controller can declare the exact shape it expects and read it type-safely.
  • Headers, decoded. request.header is a rich Header object with helpers for content type, auth, cookies, IP, and a parsed userAgent.
  • Locale out of the box. The request resolves a lang from the query string, a custom header, or Accept-Language — ready for translation without extra work.
  • Files as first-class. Multipart uploads are parsed into IRequestFile instances on request.files, each with size, type, format detection, read methods, and a write to disk.

How it works

The framework reads the native Request, extracts route params from the matched route and the decoded body, then constructs the HttpRequest. During construction it parses the URL (path, host, queries), normalizes the method, wraps the headers, parses the user agent, builds a RequestFile for every uploaded file, and resolves the locale.
SourceBecomes
request.url stringurl (an IUrl), path, host, queries
request.methodmethod (uppercased HttpMethodType)
request.headersheader (a Header) and userAgent
Matched routeparams (route parameters)
Decoded bodypayload (JSON / form fields)
Multipart FormDataform and files (each an IRequestFile)
lang/locale query, X-Custom-Lang, Accept-Languagelang (a LocaleInfoType)

Properties and methods

context.request exposes these read-only members:
MemberTypeDescription
nativeReadonly<Request>The underlying native Request.
urlIUrlParsed URL with helpers (getPath, getQuery, getQueries, getHostname, …).
pathstringThe URL path, e.g. /users/123.
methodHttpMethodTypeUppercased HTTP method, e.g. GET, POST.
hoststringHost from the Host header (empty string if absent).
ipstring | nullClient IP address, if provided.
headerHeaderHeader accessor with typed convenience getters.
userAgentIUserAgent | nullParsed user agent (browser, engine, os, device, cpu).
paramsConfig["params"]Route parameters from the matched route.
queriesConfig["queries"]Query string parameters as a record.
payloadConfig["payload"]Decoded request body (JSON object or form fields).
formFormData | nullRaw multipart form data, when present.
filesRecord<string, IRequestFile>Uploaded files keyed by form field name.
langLocaleInfoTypeDetected locale { code, region }.

Reading query and route params

Route params come from the dynamic segments of the matched route (/users/:id). Query params come from the URL query string (?page=2&search=alice). Read them from the request, or from the context.params / context.queries shortcuts.
import type { ContextType } from "@ooneex/controller";

public async handler(context: ContextType): Promise<ContextType> {
  // Route param: /users/:id
  const id = context.request.params?.id;

  // Query params: ?page=2&search=alice (same as context.queries)
  const page = context.request.queries?.page ?? 1;
  const search = context.request.queries?.search;

  // The IUrl helper reads a single query value directly
  const sort = context.request.url.getQuery("sort");

  return context;
}

Reading the payload

For POST, PUT, and PATCH requests the decoded body is on payload (and mirrored on context.payload). For JSON requests it is the parsed object; for form submissions it is the field record.
public async handler(context: ContextType): Promise<ContextType> {
  const { name, email } = context.request.payload ?? {};

  context.response.json({ created: { name, email } });

  return context;
}
Type the payload at the controller level so reads are checked:
import type { IRequest } from "@ooneex/http-request";

type CreateUserConfig = {
  payload: { name: string; email: string };
  params: { id: string };
};

const request = context.request as IRequest<CreateUserConfig>;
const { name, email } = request.payload; // typed
Validate the payload before trusting it — see Validation.

Headers and user agent

request.header is a Header object. Use get(name) and has(name) for raw values, or the typed convenience getters for common headers.
public async handler(context: ContextType): Promise<ContextType> {
  // Raw header access
  const custom = context.request.header.get("X-Request-Id");
  const hasAuth = context.request.header.has("Authorization");

  // Typed convenience getters
  const token = context.request.header.getBearerToken();
  const contentType = context.request.header.getContentType();
  const cookies = context.request.header.getCookies();

  // Parsed user agent
  const ua = context.request.userAgent;
  const browser = ua?.browser; // { name, version, ... }
  const os = ua?.os;

  return context;
}
The user agent is parsed once during construction into an IUserAgent with browser, engine, os, device, and cpu. It is null when no User-Agent header is present.

File uploads

When the request is multipart form data, the framework parses it into request.form (the raw FormData) and request.files — a record keyed by form field name where each value is an IRequestFile. A RequestFile wraps the native File and adds metadata, format detection, read methods, and disk writing.
public async handler(context: ContextType): Promise<ContextType> {
  const avatar = context.request.files.avatar;

  if (avatar && avatar.isImage && avatar.size <= 2_000_000) {
    // Persist to disk using the generated, collision-safe name
    await avatar.write(`./uploads/${avatar.name}`);
  }

  // Iterate over every uploaded file
  for (const [field, file] of Object.entries(context.request.files)) {
    console.log(field, file.originalName, file.type, file.size);
  }

  return context;
}

RequestFile API

MemberTypeDescription
idstringUnique 25-character identifier.
namestringGenerated collision-safe name, {id}.{extension}.
originalNamestringOriginal name, kebab-cased, extension preserved.
typeMimeTypeMIME type with charset stripped.
sizenumberSize in bytes.
extensionstringLowercased file extension.
isImage / isSvg / isVideo / isAudiobooleanMedia format detection.
isPdf / isText / isExcel / isCsv / isJson / isXml / isHtmlbooleanDocument format detection.
readAsArrayBuffer()Promise<ArrayBuffer>Read the contents as an ArrayBuffer.
readAsStream()ReadableStream<Uint8Array>Stream the contents (for large files).
readAsText()Promise<string>Read the contents as text.
write(path)Promise<void>Write the file to disk at path.
const upload = context.request.files.document;

if (upload.isText || upload.isCsv) {
  const text = await upload.readAsText();
}

if (upload.isImage && !upload.isSvg) {
  const buffer = await upload.readAsArrayBuffer();
}
For organizing uploads, derived paths, and storage backends, see Storage.

Language and locale detection

Every request resolves a lang ({ code, region }) during construction, with a fixed precedence:
PrioritySourceExample
1lang query parameter?lang=fr
2locale query parameter?locale=de
3X-Custom-Lang headerX-Custom-Lang: es
4Accept-Language headerAccept-Language: en-US,en;q=0.9
When the locale comes from a query param or the custom header, region is null and only code is set. When it falls back to Accept-Language, the first language is parsed into code and region (e.g. { code: "en", region: "US" }). If nothing matches, it defaults to en-US.
public async handler(context: ContextType): Promise<ContextType> {
  const { code, region } = context.request.lang; // e.g. { code: "fr", region: null }

  context.response.json({ locale: code, region });

  return context;
}

Best practices

  • Prefer the shortcuts. Read context.params, context.queries, and context.payload for everyday access; reach for context.request for headers, IP, files, and locale.
  • Type the request. Declare a Config for IRequest<Config> (or cast) so params, queries, and payload are checked, not any.
  • Validate before you trust. Query and body values are user input — run them through Validation before acting on them.
  • Never mutate. Every member of IRequest is read-only; build your reply on context.response, not by changing the request.
  • Use the safe file name. Save uploads under file.name (the generated, collision-safe name), not the user-supplied originalName, and check size and the is* flags before writing.
  • Let locale flow. Read request.lang rather than re-parsing Accept-Language yourself; it already applies the documented precedence.
  • Controllers — where the request reaches your handler via context.
  • Validation — validate params, queries, and payload before use.
  • Response — build the reply with context.response.
  • Storage — persist uploaded files beyond a single request.