Docs/Routing & Data/Server Request APIs

Routing & Data

Server Request APIs

Server Request APIs — Routing & Data documentation for BCP Framework.

Last updated: 2026-08-29T01:11:41

Server Request APIs

BCP Framework 0.1.7 introduces request-scoped server helpers through bcp/server.

These APIs are server-only and are intended for API route handlers in the current 0.1.x client pipeline. Importing bcp/server from a page, layout, client island or another module reachable from the browser bundle is rejected by the client-boundary validator.

Headers

Use headers() to read a copy of the current request headers:

TypeScript
import {
    headers,
} from "bcp/server";

export async function GET() {
    const requestHeaders =
        await headers();

    return Response.json({
        userAgent:
            requestHeaders.get(
                "user-agent"
            ),
        requestId:
            requestHeaders.get(
                "x-request-id"
            ),
    });
}

The returned Headers object is a copy. Mutating it does not modify the original incoming request.

Request URL

Use requestUrl() to read the complete URL of the current request, including pathname and search parameters:

TypeScript
import {
    json,
    requestUrl,
} from "bcp/server";

export async function GET() {
    const url =
        await requestUrl();

    return json({
        pathname:
            url.pathname,
        query:
            url.searchParams.get(
                "query"
            ),
    });
}

The returned URL is a copy. Mutating it does not change the request context seen by another helper call.

Request method

requestMethod() returns the normalized uppercase HTTP method for the active request:

TypeScript
import {
    requestMethod,
} from "bcp/server";

export async function POST() {
    return Response.json({
        method:
            await requestMethod(),
    });
}

A POST request returns "POST", a GET request returns "GET", and so on.

Request ID

requestId() provides one stable identifier for the active request:

TypeScript
import {
    requestId,
} from "bcp/server";

export async function GET() {
    const id =
        await requestId();

    return Response.json({
        requestId:
            id,
    });
}

When the incoming request includes a valid X-Request-Id, BCP preserves that value. If the header is missing, blank, longer than 256 characters or contains control characters, BCP generates a UUID once and every requestId() call in that request returns the same generated value.

Bearer tokens

Use bearerToken() to extract an HTTP Bearer credential from the Authorization header:

TypeScript
import {
    bearerToken,
} from "bcp/server";

export async function GET() {
    const token =
        await bearerToken();

    if (!token) {
        return Response.json(
            {
                error:
                    "Unauthorized",
            },
            {
                status:
                    401,
            }
        );
    }

    return Response.json({
        authenticated:
            true,
    });
}

The Bearer scheme is matched case-insensitively. BCP returns the credential string after the scheme and does not decode, verify or assume JWT. Missing authorization and non-Bearer schemes return null.

Client IP

clientIp() reads the direct connection address attached to the BCP request context:

TypeScript
import {
    clientIp,
} from "bcp/server";

export async function GET() {
    return Response.json({
        ip:
            await clientIp(),
    });
}

Proxy forwarding headers are not trusted by default. Applications behind a trusted reverse proxy can opt in explicitly:

TypeScript
const ip =
    await clientIp({
        trustProxy:
            true,
    });

When trustProxy is enabled, BCP checks client-address sources in this order:

  1. the standardized Forwarded header (for=)
  2. the first usable value in X-Forwarded-For
  3. X-Real-IP
  4. the direct connection address

Do not enable trustProxy when clients can connect directly without a trusted proxy stripping or replacing forwarding headers, because forwarded client-IP headers can otherwise be spoofed.

IPv4-mapped IPv6 addresses such as ::ffff:127.0.0.1 are normalized to 127.0.0.1.

JSON responses

json() is a small wrapper around the standard Web Response.json() API. It accepts the response body and an optional ResponseInit:

TypeScript
import {
    json,
} from "bcp/server";

export function POST() {
    return json(
        {
            success: true,
        },
        {
            status: 201,
            headers: {
                "x-created": "yes",
            },
        }
    );
}

BCP keeps the Web Response model, so existing Response objects and Response.json() continue to work alongside the helper.

Redirect responses

Use redirect() to create HTTP redirects. Relative destinations are resolved against the current request URL:

TypeScript
import {
    redirect,
} from "bcp/server";

export function POST() {
    return redirect(
        "/dashboard",
        303
    );
}

The default status is 307. Supported redirect statuses are 301, 302, 303, 307 and 308. Redirect destinations must use HTTP or HTTPS. Absolute HTTP/HTTPS URLs are also supported.

A common login-style flow can combine response cookies and redirects:

TypeScript
import {
    cookies,
    redirect,
} from "bcp/server";

export async function POST() {
    const cookieStore =
        await cookies();

    cookieStore.set(
        "session",
        "session-token",
        {
            httpOnly: true,
            secure: true,
            sameSite: "lax",
            path: "/",
        }
    );

    return redirect(
        "/dashboard",
        303
    );
}

BCP applies pending response cookies after the handler resolves, so cookies created before redirect() are preserved on the redirect response.

Request cookies

Use cookies() to inspect cookies sent by the client:

TypeScript
import {
    cookies,
} from "bcp/server";

export async function GET() {
    const cookieStore =
        await cookies();

    return Response.json({
        session:
            cookieStore.get(
                "session"
            )?.value ?? null,
        hasTheme:
            cookieStore.has(
                "theme"
            ),
        all:
            cookieStore.getAll(),
    });
}

Request cookie values are percent-decoded when possible. Invalid percent encoding is preserved instead of failing the request.

Setting response cookies

cookies().set() records a Set-Cookie value that BCP applies to the API response:

TypeScript
import {
    cookies,
} from "bcp/server";

export async function POST() {
    const cookieStore =
        await cookies();

    cookieStore.set(
        "session",
        "session-token",
        {
            httpOnly: true,
            secure: true,
            sameSite: "lax",
            path: "/",
            maxAge: 60 * 60 * 12,
        }
    );

    return Response.json({
        success: true,
    });
}

The object form is also supported:

TypeScript
cookieStore.set({
    name: "refreshToken",
    value: "token-value",
    httpOnly: true,
    secure: true,
    sameSite: "strict",
    path: "/api",
    expires:
        new Date(
            Date.now() +
            7 * 24 * 60 * 60 * 1000
        ),
});

Supported response-cookie options are:

  • domain
  • path
  • expires
  • maxAge
  • httpOnly
  • secure
  • sameSite: "lax", "strict" or "none"

Multiple response cookies remain separate Set-Cookie header values. Cookies already present on the returned Response are preserved.

Deleting cookies

Deleting a cookie emits an expired cookie with Max-Age=0:

TypeScript
export async function DELETE() {
    const cookieStore =
        await cookies();

    cookieStore.delete(
        "session"
    );

    return Response.json({
        success: true,
    });
}

When the original cookie uses a custom path or domain, supply the same values when deleting it:

TypeScript
cookieStore.delete(
    "session",
    {
        path: "/admin",
        domain: "example.com",
    }
);

Request isolation

BCP stores request state with Node.js AsyncLocalStorage. Concurrent requests receive independent URL, method, request ID, headers and cookies, so request data from one user is not shared with another request.

Calling a request-scoped helper without an active BCP request context throws an error. A relative redirect() also requires the active request context because it resolves the destination against the current URL.

Client boundary

This is invalid in the current 0.1.x page/client pipeline:

TypeScript
import {
    cookies,
} from "bcp/server";

export default function Page() {
    return null;
}

Move the server operation behind an API route instead:

text
page/client
    -> fetch("/api/session")
    -> API route
    -> bcp/server

Future server-rendered data primitives can extend these helpers to page-level server execution without exposing them to the browser bundle.