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:
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:
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:
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:
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:
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:
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:
const ip =
await clientIp({
trustProxy:
true,
});When trustProxy is enabled, BCP checks client-address sources in this order:
- the standardized
Forwardedheader (for=) - the first usable value in
X-Forwarded-For X-Real-IP- 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:
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:
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:
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:
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:
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:
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:
domainpathexpiresmaxAgehttpOnlysecuresameSite:"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:
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:
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:
import {
cookies,
} from "bcp/server";
export default function Page() {
return null;
}Move the server operation behind an API route instead:
page/client
-> fetch("/api/session")
-> API route
-> bcp/serverFuture server-rendered data primitives can extend these helpers to page-level server execution without exposing them to the browser bundle.