> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ooneex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Request

> Read the incoming HTTP request inside a controller — URL, method, headers, query and route params, JSON payload, client IP, locale, and uploaded files.

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.

| Source                                                    | Becomes                                      |
| --------------------------------------------------------- | -------------------------------------------- |
| `request.url` string                                      | `url` (an `IUrl`), `path`, `host`, `queries` |
| `request.method`                                          | `method` (uppercased `HttpMethodType`)       |
| `request.headers`                                         | `header` (a `Header`) and `userAgent`        |
| Matched route                                             | `params` (route parameters)                  |
| Decoded body                                              | `payload` (JSON / form fields)               |
| Multipart `FormData`                                      | `form` and `files` (each an `IRequestFile`)  |
| `lang`/`locale` query, `X-Custom-Lang`, `Accept-Language` | `lang` (a `LocaleInfoType`)                  |

## Properties and methods

`context.request` exposes these read-only members:

| Member      | Type                           | Description                                                                        |
| ----------- | ------------------------------ | ---------------------------------------------------------------------------------- |
| `native`    | `Readonly<Request>`            | The underlying native `Request`.                                                   |
| `url`       | `IUrl`                         | Parsed URL with helpers (`getPath`, `getQuery`, `getQueries`, `getHostname`, ...). |
| `path`      | `string`                       | The URL path, e.g. `/users/123`.                                                   |
| `method`    | `HttpMethodType`               | Uppercased HTTP method, e.g. `GET`, `POST`.                                        |
| `host`      | `string`                       | Host from the `Host` header (empty string if absent).                              |
| `ip`        | `string \| null`               | Client IP address, if provided.                                                    |
| `header`    | `Header`                       | Header accessor with typed convenience getters.                                    |
| `userAgent` | `IUserAgent \| null`           | Parsed user agent (browser, engine, os, device, cpu).                              |
| `params`    | `Config["params"]`             | Route parameters from the matched route.                                           |
| `queries`   | `Config["queries"]`            | Query string parameters as a record.                                               |
| `payload`   | `Config["payload"]`            | Decoded request body (JSON object or form fields).                                 |
| `form`      | `FormData \| null`             | Raw multipart form data, when present.                                             |
| `files`     | `Record<string, IRequestFile>` | Uploaded files keyed by form field name.                                           |
| `lang`      | `LocaleInfoType`               | Detected 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.

```typescript theme={null}
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.

```typescript theme={null}
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:

```typescript theme={null}
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](/basics/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.

```typescript theme={null}
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.

```typescript theme={null}
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

| Member                                                                   | Type                         | Description                                        |
| ------------------------------------------------------------------------ | ---------------------------- | -------------------------------------------------- |
| `id`                                                                     | `string`                     | Unique 25-character identifier.                    |
| `name`                                                                   | `string`                     | Generated collision-safe name, `{id}.{extension}`. |
| `originalName`                                                           | `string`                     | Original name, kebab-cased, extension preserved.   |
| `type`                                                                   | `MimeType`                   | MIME type with charset stripped.                   |
| `size`                                                                   | `number`                     | Size in bytes.                                     |
| `extension`                                                              | `string`                     | Lowercased file extension.                         |
| `isImage` / `isSvg` / `isVideo` / `isAudio`                              | `boolean`                    | Media format detection.                            |
| `isPdf` / `isText` / `isExcel` / `isCsv` / `isJson` / `isXml` / `isHtml` | `boolean`                    | Document 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`.                  |

```typescript theme={null}
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](/components/storage).

## Language and locale detection

Every request resolves a `lang` (`{ code, region }`) during construction, with a fixed precedence:

| Priority | Source                   | Example                           |
| -------- | ------------------------ | --------------------------------- |
| 1        | `lang` query parameter   | `?lang=fr`                        |
| 2        | `locale` query parameter | `?locale=de`                      |
| 3        | `X-Custom-Lang` header   | `X-Custom-Lang: es`               |
| 4        | `Accept-Language` header | `Accept-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`.

```typescript theme={null}
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](/basics/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.

## Related

* [Controllers](/basics/controller) — where the request reaches your handler via `context`.
* [Validation](/basics/validation) — validate `params`, `queries`, and `payload` before use.
* [Response](/basics/response) — build the reply with `context.response`.
* [Storage](/components/storage) — persist uploaded files beyond a single request.
