Skip to content

Request Composables

Composables for reading incoming HTTP request data: headers, query parameters, cookies, authorization, client IP, body size limits.

Raw Request Instance

To get a reference to the raw request instance, you can use the useRequest composable function. However, in most cases, you won't need to directly access the raw request instance unless you're developing a new feature or require low-level control over the request.

js
import { useRequest } from '@wooksjs/event-http'

app.get('/test', () => {
    const { raw } = useRequest()
    // Access the raw request instance if needed
})

URI Parameters

URI parameters are automatically parsed by the router and are covered in the Retrieving URI Parameters section.

Query Parameters

The useUrlParams composable provides three functions for working with query parameters:

  • params() — returns an instance of WooksURLSearchParams, which extends the standard URLSearchParams with a toJson method that returns a JSON object of the query parameters.
  • toJson() — is a shortcut for params().toJson(), returning the query parameters as a JSON object.
  • raw() — returns the raw search parameter string, such as ?param1=value&....
js
import { useUrlParams } from '@wooksjs/event-http'

app.get('hello', () => {
    const { params, toJson, raw } =
        useUrlParams()

    // curl http://localhost:3000/hello?name=World
    console.log(toJson()) // { name: 'World' }
    console.log(raw()) // ?name=World

    return `Hello ${params().get('name')}!`
})

Example usage with cURL:

bash
curl http://localhost:3000/hello?name=World
# Hello World!

Arrays and safe toJson() conversion

params() returns a WooksURLSearchParams — a subclass of the standard URLSearchParams whose only addition is a typed toJson<T>() method. The standard accessors (get, getAll, has, entries, …) work exactly as in Node's URLSearchParams.

toJson() follows a few rules that protect against malformed or hostile query strings:

  • Arrays use a trailing []. A key ending in [] collects all its values into a string[], and the [] is kept in the resulting key. So ?tags[]=a&tags[]=b becomes { 'tags[]': ['a', 'b'] }.
  • Repeated plain keys throw. A non-[] key that appears more than once raises HttpError(400) (Duplicate key). Use the [] form when you expect multiple values; if you need the raw repeated values without the array convention, read them with params().getAll('key') instead of toJson().

toJson() also rejects the prototype-pollution keys __proto__, constructor, and prototype with HttpError(400), and builds a null-prototype object.

js
// ?status=open&tags[]=urgent&tags[]=api
const query = useUrlParams().toJson()
// { status: 'open', 'tags[]': ['urgent', 'api'] }

Method and Headers

The useRequest composable provides additional shortcuts for accessing useful data related to the request, such as the URL, method, headers, and the raw request body.

js
import { useRequest } from '@wooksjs/event-http'

app.get('/test', async () => {
    const {
        url, // Request URL (string)
        method, // Request method (string)
        headers, // Request headers (object)
        rawBody, // Request body (() => Promise<Buffer>)
        reqId, // Per-event UUID (() => string)
        isCompressed, // Whether the body is gzip/deflate/br compressed (() => boolean)
    } = useRequest()

    const body = await rawBody() // Body as a Buffer
})

reqId() returns a stable per-event UUID — the same value as useEventId().getId() from @wooksjs/event-core.

useHeaders() is a shortcut that returns the request headers record directly (IncomingHttpHeaders, lowercased names) — equivalent to useRequest().headers:

js
import { useHeaders } from '@wooksjs/event-http'

app.get('/test', () => {
    const { 'content-type': contentType } = useHeaders()
})

Client IP

useRequest() provides two helpers for resolving the client's IP address:

js
import { useRequest } from '@wooksjs/event-http'

app.get('/test', () => {
    const { getIp, getIpList } = useRequest()

    getIp() // Socket remote address
    getIp({ trustProxy: true }) // First `x-forwarded-for` entry, falling back to the socket address

    getIpList()
    // {
    //     remoteIp: '...',    // socket remote address
    //     forwarded: ['...'], // all `x-forwarded-for` entries
    // }
})

By default getIp() returns the socket's remote address and ignores x-forwarded-for, which any client can spoof. Pass { trustProxy: true } only when your app runs behind a trusted reverse proxy that sets that header.

Cookies

Cookies are not automatically parsed unless requested. The useCookies composable function provides a cookie getter and access to the raw cookies string.

js
import { useCookies } from '@wooksjs/event-http'

app.get('/test', async () => {
    const {
        raw, // Raw "cookie" from headers (string | undefined)
        getCookie, // Cookie getter ((name) => string | null)
    } = useCookies()

    console.log(getCookie('session'))
    // Prints the value of the cookie with the name "session"
})

Authorization

The useAuthorization function provides helpers for working with authorization headers:

js
import { useAuthorization } from '@wooksjs/event-http'

app.get('/test', async () => {
    const {
        authorization, // The raw value of the "authorization" header (string | undefined)
        type, // The authentication type (Bearer/Basic) (() => string | null)
        credentials, // The credentials that follow the auth type (() => string | null)
        is, // Checks auth type: is('basic'), is('bearer'), etc. ((type) => boolean)
        basicCredentials, // Parsed basic auth credentials (() => { username, password } | null)
    } = useAuthorization()

    if (is('basic')) {
        const { username, password } = basicCredentials()
        console.log({ username, password })
    } else if (is('bearer')) {
        const token = credentials()
        console.log({ token })
    } else {
        // Unknown or empty authorization header
    }
})

Note: authorization is a plain string value. All other properties are lazy functions — they compute on first call and cache the result. The is() argument is typed as KnownAuthType ('basic' | 'bearer'), but any other string is also accepted.

Accept Header

The useAccept composable checks the request's Accept header for MIME type support:

js
import { useAccept } from '@wooksjs/event-http'

app.get('/test', () => {
    const { accept, has } = useAccept()

    // Use short names for common types
    if (has('json')) {
        return { data: 'json response' }
    } else if (has('html')) {
        return '<p>html response</p>'
    }

    // Or full MIME types for anything else
    if (has('image/webp')) {
        // ...
    }
})

Short names: 'json' (application/json), 'html' (text/html), 'xml' (application/xml), 'text' (text/plain) — typed as KnownAcceptType. Full MIME strings are also accepted.

The accept property returns the raw Accept header value.

Body Size Limits

Request body reading is protected by configurable limits:

LimitDefaultDescription
maxCompressed1 MBMax compressed body size in bytes
maxInflated10 MBMax decompressed body size in bytes
maxRatio100Max compression ratio (zip-bomb protection)
readTimeoutMs10 000 msBody read timeout (0 disables the timeout)

The default values are exported as DEFAULT_LIMITS from @wooksjs/event-http.

rawBody() transparently decompresses gzip, deflate, and br request bodies (including stacked encodings) and always resolves to the decompressed Buffer, cached across calls — that is why the maxInflated and maxRatio limits exist.

App-level configuration

js
import { createHttpApp } from '@wooksjs/event-http'

const app = createHttpApp({
    requestLimits: {
        maxCompressed: 50 * 1024 * 1024,  // 50 MB
        maxInflated: 100 * 1024 * 1024,   // 100 MB
        maxRatio: 200,
        readTimeoutMs: 30_000,
    },
})

Per-request overrides

Override limits inside a handler before reading the body:

js
import { useRequest } from '@wooksjs/event-http'

app.post('/upload', async () => {
    const {
        setMaxCompressed,
        setMaxInflated,
        setMaxRatio,
        setReadTimeoutMs,
        rawBody,
    } = useRequest()

    // Raise limits for this route only
    setMaxCompressed(50 * 1024 * 1024)  // 50 MB
    setMaxInflated(100 * 1024 * 1024)   // 100 MB

    const body = await rawBody()
    return { size: body.length }
})

Per-request setters use copy-on-write — they do not mutate the app-level configuration. Each setter has a matching getter (getMaxCompressed(), getMaxInflated(), getMaxRatio(), getReadTimeoutMs()) returning the currently effective value.

What happens on violation

When a limit is violated, rawBody() throws an HttpError that the framework renders automatically as an error response:

ConditionError
Body exceeds maxCompressed (or maxInflated when uncompressed)413 Payload Too Large
Decompressed body exceeds maxInflated413 Inflated body too large
Compression ratio exceeds maxRatio413 Compression ratio too high
Unsupported Content-Encoding415 Unsupported Content-Encoding
No data received for readTimeoutMs (the timer resets on every received chunk)408 Request body timeout

Body Parser

The implementation of the body parser is isolated into a separate package called @wooksjs/http-body. For more details on using the body parser, refer to the Body Parser section.

Released under the MIT License.