Request and response
This page details the complete API of VextJS's request object VextRequest and response object VextResponse.
For a first endpoint, read Routing; use this page to look up members. Unless a file path is shown, app.get/post/... examples belong inside defineRoutes((app) => { ... }), and req/res fragments belong in the corresponding handler or middleware. They are not standalone entry files. Standard CRUD Response below provides a complete route and config for checking combined usage.
VextRequest
VextRequest is the unified request interface supplied by adapters. Public members support reusable business code; for TLS, raw requests, and extension fields, check the selected adapter's behavior.
Public member list
method
HTTP request method, always an uppercase string.
url
The request path and query string, such as /users?page=1; do not treat it as an absolute URL with scheme and host.
path
The path portion of the URL, excluding the query string.
route
The route registration template matched by the current request is automatically injected by each Adapter after the route is matched. The difference from path is that path is the actual request path (high cardinality), and route is the routing template (low cardinality).
This is a key property in solving the high cardinality problem of metrics systems like Prometheus - metrics should be aggregated by routing templates, not actual paths.
params
Path dynamic parameters. Automatically parsed by the route matching engine.
The value of params is always of type string. If a numeric type is required, use validate + req.valid('param') to obtain the value after automatic type conversion.
query
Parsed URL query key-value pairs. For repeated keys such as ?tag=a&tag=b, the first value wins ({ tag: "a" }); parse the query part of req.url explicitly if multiple values are required.
The value of query is always of type string. After using validate to configure query verification, the value after automatic type conversion (such as string '2' → number 2) can be obtained through req.valid('query').
body
The request body data is parsed and filled by the built-in body-parser middleware.
- Before
body-parsermiddleware is executed,bodyisundefined - Supports
application/jsonandapplication/x-www-form-urlencodedformats - You can limit the request body size through
config.bodyParser.maxBodySize
Successful JSON parsing does not validate fields against a schema. Business handlers should declare validate.body and read req.valid("body"). Multipart files are in req.files; see files and the Uploads Guide for enablement and limits.
headers
Request header object, all keys are lowercase.
app
The VextApp application instance to which the current request belongs.
Route handlers usually access app directly through the closure of defineRoutes. But routing-level middleware does not have closures, and the framework capabilities must be accessed through req.app:
Capabilities accessible via req.app:
signal
An AbortSignal bound to the request lifecycle. It is aborted when the client disconnects while the request is still pending. Reading the complete request body or completing the response normally does not abort it. When the route timeout middleware is active, its deadline signal is combined with the connection signal, so downstream work observes either cancellation source.
Pass the signal to APIs that support cancellation and still stop mutating application or response state after it is aborted:
requestId
Request unique identifier for log correlation and distributed link tracing.
Generate rules:
- When enabled, a nonempty incoming header named by
config.requestId.header(defaultx-request-id) takes precedence over custom generators. - Otherwise use the generator from
app.setRequestIdGenerator(), thenconfig.requestId.generate, then a default UUID v4. - The final ID must be 1–512 characters without control characters or an error is thrown. With
requestId.enabled: false, the value is""and no requestId response header is written.
The default response header is also x-request-id, configurable through requestId.responseHeader. A client-supplied ID is a correlation marker, not a framework-guaranteed globally unique value.
ip
Client IP address.
Enable trustProxy: true only when the trusted ingress proxy overwrites forwarding headers correctly; otherwise a client can influence the value. With it off, the address is that of the direct peer. Hono falls back to 127.0.0.1 when a Node socket address is unavailable; other Node adapters also use that fallback when the address is missing.
protocol
Request protocol.
valid(location)
Get the data after validate verification and type conversion.
TValidated is generated from the validate object on the current route.
The public API also keeps an explicit generic override for dynamic or external
schemas, but ordinary route code should rely on the inferred contract.
Parameters:
location and data source mapping:
Note that location uses the singular 'param' (consistent with the key configured in validate), but the underlying data source is the plural req.params. The framework maps them correctly.
Basic Usage:
Automatic inference:
If a route does not declare the requested location, its inferred result is
undefined. Chainable field builders are inferred as unknown; use a runtime
type guard or an explicit override only when the application owns that dynamic
contract.
Multiple location verification:
Only a location declared in options.validate and already validated has a result. An undeclared location, or a preceding route middleware that has not reached validation, gets undefined. Explicit generics change types only, not runtime validation. Header results contain only declared fields with lowercase keys; conversion and extra-field behavior for other locations depend on the validator. See Validation.
onClose(handler)
Register a request close hook that runs once when the response completes or the client disconnects early. Hooks registered after the request ends run immediately. A hook running after normal completion does not mean that req.signal was aborted.
Use this for stream resources on normal completion or early disconnect:
The callback type is synchronous () => void; the framework does not await async cleanup. It releases registered callback references afterward. Your callback still must clear its own timers, listeners, and other resources.
t(key, params?)
Optional translation-function extension. Built-in locale loading and request negotiation do not automatically inject t onto req. Use it only if an application plugin explicitly sets it; merely configuring locale or adding src/locales is insufficient. See I18n.
Usage:
files
File upload list, initially undefined. Vext populates it when built-in multipart parsing is enabled globally through config.multipart.enabled or for one route through multipart.enabled: true. A route may set multipart.enabled: false to opt out even when global parsing is enabled. Built-in parsing keeps the request body and each buffer in memory; it does not create framework-managed temporary files, a temp directory, TTL, or periodic cleanup job. Custom upload plugins can also populate this field when they need streaming writes, durable storage, or third-party parsers.
multipart.files also drives OpenAPI multipart/form-data requestBody generation and required-file runtime checks. Uploads still obey maxFiles, maxFileSize, and allowedMimeTypes.
A per-file limit does not enlarge the whole-request limit. For a 10 MB file, set bodyParser.maxBodySize reasonably above it to account for multipart boundaries.
cookies and cookie(name)
cookies is a read-only Readonly<Record<string, string>> parsed from the Cookie header without needing Session. For repeated names the first wins. Values are decoded with decodeURIComponent; failed decoding retains the raw value. Reserved names __proto__, constructor, and prototype are dropped. cookie(name) returns one value or undefined.
Reading a Cookie does not authenticate a user. Use response res.cookie() / res.clearCookie() to write or expire one; see Cookies and Session.
csrfToken()
Returns the token for this request only when config.csrf.enabled or a manually registered csrf() middleware has run; otherwise it throws. Repeated reads within the request use one token. Generation sets Cache-Control: no-store. Automatic storage mode depends on Session presence; signed-cookie mode needs a secret. Calling this function does not replace token submission and validation for protected requests.
See Security for enablement, submission headers, and failure behavior.
auth
Every request starts with anonymous VextAuthContext: isAuthenticated: false and empty roles/scopes/claims. Authentication middleware fills identity after calling an app-provided verifier; route auth guards then enforce access. docs.security does not establish identity.
See Security for auth plus guard. Presence of req.auth does not mean the request is logged in.
session
With Session middleware, req.session is VextSession; otherwise undefined. Besides business fields, it exposes read-only id/isNew/isDestroyed and async save(), regenerate(), destroy().
See Cookies and Session for automatic commit and Store settings. Save before starting a stream or download so persistence and Cookie commit occur before headers; do not attempt to save after streaming has begun.
Extended fields
Middleware and plugins can mount custom fields on req. Type hints are available through the declare module extension interface:
Include that declaration in the project's TypeScript config; see Project Structure. Types do not assign runtime values. The verifyToken below is an app-owned function whose implementation must be imported, and load-user must be in the middleware allowlist.
VextResponse
VextResponse is the unified response object interface of the framework. Provides JSON response, text response, streaming response, redirection and other capabilities.
List of methods
render() and renderError() are bound by the built-in frontend renderer. sse() and upgrade() are optional extension points and are available only when the corresponding plugin installs them. Cookie methods append separate Set-Cookie headers and preserve multiple cookies.
json(data, status?)
Returns a JSON response. This is the most common response method.
Parameters:
Export Packaging:
When config.response.wrap is true (default), res.json(data) is automatically wrapped:
When config.response.wrap is false, send raw data directly:
Specify status code:
204 No Content:
Regardless of whether the wrapper is opened or not, the 204 status code does not send the message body (conforming to RFC 9110 §15.3.5):
HEAD also sends no body. If a route declares runtime responses, JSON serialization chooses an exact status schema, then status family, then default; the schema describes business data, while the framework applies the output wrapper. Without a matching schema, ordinary JSON behavior remains. docs.responses documents but does not validate runtime output. See the response contract.
Error response (usually handled automatically by the framework error-handler):
text(content, status?)
Returns a plain text response, without export wrapping.
Automatically set Content-Type: text/plain; charset=utf-8.
Omitting status uses the current res.statusCode, initially 200.
render(page, props?, options?)
Renders a built-in frontend page when config.frontend.enabled and the matching page and build/dev output exist. page is a page ID under src/frontend/pages, such as dashboard, not a URL or absolute file path. URLs still come from route files. Calling it with frontend disabled throws.
VextRenderOptions includes status, headers, head, seo, nonce, locale, messages, ssr, layout, and layoutData. Status uses the current response status by default. Props, layoutData, and messages must be safely JSON serializable. See Routing and Pages, Rendering Modes, and SEO. This HTML response does not use the JSON { code, data, requestId } wrapper.
renderError(errorOrStatus?, pageOrOptions?, options?)
The frontend renderer creates an error page. The first argument can be Error, HTTP status, or error-code string; the second can be a page ID or VextRenderErrorOptions, with the third supplying more options. Without a matching custom error page, Vext uses its built-in error document. Frontend must be enabled.
VextRenderErrorOptions adds page, props, code, message, details, and expose. A compatibility signature also accepts a plain object or array in the second argument. An object without render-option keys, or an array, is treated as error details, not as props. See Errors and Document for page selection and exposure.
stream(readable, contentType?)
Streaming responses for large file transfers or real-time data streaming.
Streams and downloads start the send flow immediately. Set status/headers and save any required Session changes first. An asynchronous read failure after sending starts cannot be rewritten as an ordinary JSON error. await next() returning does not mean the entire stream finished; use req.onClose() for cleanup.
Parameters:
SSE (Server-Sent Events):
download(readable, filename, contentType?)
In file download responses, the Content-Disposition: attachment header is automatically set. ASCII-safe filenames keep the plain filename output; filenames containing non-ASCII characters, quotes, path separators, or control characters get a safe fallback plus a UTF-8 filename* value.
Parameters:
The browser handles this according to its own download settings; a dialog is not guaranteed.
redirect(url, status?)
HTTP redirect.
Parameters:
Redirect status code description:
Non-ASCII Location bytes are encoded; CR/LF/NUL are rejected. A runtime value outside the allowed status union falls back to 302.
status(code)
Set HTTP status code and support chain calls.
If status() is not called, the default status code is 200. It can also be set directly through the second parameter of json(data, status).
setHeader(name, value)
Set response headers to support chain calls.
Common response headers:
Array values can set multiple Set-Cookie headers; do not comma-join them into one Cookie. Prefer the dedicated methods below for normal Cookie operations.
cookie(name, value, options?) and clearCookie(name, options?)
Both return this and append a valid or expired Set-Cookie header. Multiple calls remain multiple headers. They do not mutate this request's req.cookies.
CookieSerializeOptions includes domain, path, expires: Date, maxAge (seconds), httpOnly, secure, sameSite (boolean or lax/strict/none), priority, partitioned, and encode. No options means no automatic path or security attributes; value encoding defaults to encodeURIComponent. To clear a Cookie, use its original path/domain; the method sets expires to Unix epoch and maxAge to zero. See Cookies and Session for a full browser round trip.
headersSent (read-only)
This means the framework response entered a terminal send flow, useful for avoiding duplicate response choices. A buffered JSON/text response may show true before socket write; a stream sends immediately. It does not mean the client received every byte.
Buffered responses commit as the onion stack unwinds, so after middleware can still add headers with setHeader(). Once streaming starts, do not rely on that. Choose status and business content before a response outlet; use statusCode for current framework status and req.onClose() for completion.
sse() and upgrade()
These are optional extension points, with signatures sse?(): unknown and upgrade?(): unknown. Core does not implement them or promise another return type. Confirm that a plugin installed them; its contract defines connection handling. The stream() SSE example above does not require an extension method.
statusCode (read-only)
Get the current HTTP status code.
Mainly used for onion model after-middleware, reading the response status code after await next():
VextPublicResponse
User-visible response type that omits rawJson() and every underscore-prefixed internal method:
Route handlers currently receive VextResponse, which includes internal methods. Application code normally does not need those APIs; VextPublicResponse is intended for wrappers and extensions that expose only the stable public response surface.
Internal Methods (Not Recommended for Direct Use)
_getRawBodyBuffer() and _getRawBody()
These internal request readers serve framework code and parser plugins. Public signatures accept an optional byte limit:
Results are cached and the raw stream consumed once. GET/HEAD/OPTIONS return empty results. maxBytes enforces a limit with 413 on excess, including when rereading cached data. The Buffer method preserves bytes; the string method decodes UTF-8.
A custom multipart parser must implement parsing, file/field limits, persistence, and ordering against built-in parsing. This Buffer API reads into memory; it is not streaming disk storage. Prefer built-in files for standard uploads.
rawJson(data, status?)
Returns raw JSON without output wrapping, for framework error handling, rate limiting, and other internal response flows.
User code should not call rawJson() directly. To bypass egress wrapping, set config.response.wrap: false and then use standard res.json().
_enableWrap()
Turn on the export packaging sign. Only called by the built-in response-wrapper middleware.
After the call, subsequent json() calls will automatically wrap the response body into the { code: 0, data, requestId } format.
Usage Patterns
Standard CRUD Response
This two-file example uses the package, TypeScript, and scripts from Quick Start. Data is in process memory and resets on restart; it demonstrates response and validation behavior.
Run npm run dev and check in order:
Expect 201 with Location, 200 for read/update, and 204 without body on delete; reading the deleted ID returns 404. Missing name returns 422 and a non-UUID path parameter returns 400. Default JSON wrapping puts the new ID at data.id. Stop dev, run npm run build -- --typecheck, start with npm start, and repeat from creation. See Services and Database for persistence and service separation.
Error handling
This fragment assumes an existing user service with findById. The complete in-memory 404 path is in the CRUD example above.
Errors thrown by app.throw() are uniformly captured by the framework error-handler middleware and converted into standard error responses:
If you need to actively return an explicit HTTP error, use app.throw(...). If there is an unexpected runtime failure, you can also directly throw new Error("..."), and the framework will capture it as 500; when response.hideInternalErrors = false, the JSON 500 response in the development environment will be additionally accompanied by stack.
Custom Response Header and Status
This only inspects an upload, so it returns 200. The complete CRUD example shows creating a resource with 201 and Location.
Streaming file download
Choose files through an app-owned key map, not by joining arbitrary user paths to a directory. The FileHandle stream closes the file on finish/destroy. A disk error after streaming starts cannot be converted to 404 by the completed open catch.
Conditional response
Add this fragment inside the CRUD defineRoutes callback above, reusing items. It illustrates an exact Accept: text/plain match, not full HTTP content negotiation.
Requests and responses in middleware
Onion model
Middleware implements the onion model through await next(), which can handle requests and responses before and after the handler is executed:
A downstream throw skips ordinary after code; put work that must run on both success and failure in finally. Timing ends when the stack unwinds, not when a stream finishes. See Middleware for registration and allowlists.
Modify request
Middleware can modify the request object before next():
The verifyJWT function below must be implemented and imported by the app; req.user uses the declaration merge above. See Security for actual authentication and guard wiring.
Short circuit response
Middleware can return the response directly without calling next() (short circuit):
You may also send a response and return. For a standard error body use app.throw(); res.status(403).json(...) still follows normal business JSON wrapping and does not automatically become the error contract.
Type import
These types usually do not need to be imported explicitly - the types of req and res are automatically inferred by TypeScript in the callbacks of defineRoutes and defineMiddleware. Explicitly imported types are only necessary when writing stand-alone utility functions: