HTTP API
The server global object is available in all Harper component code. It provides access to the HTTP server middleware chain, WebSocket server, authentication, resource registry, and cluster information.
server.http(listener, options)
Add a handler to the HTTP request middleware chain.
server.http(listener: RequestListener, options?: HttpOptions): HttpServer[]
Returns an array of HttpServer instances based on the options.port and options.securePort values.
Example:
server.http(
(request, next) => {
if (request.url === '/graphql') return handleGraphQLRequest(request);
return next(request);
},
{ runFirst: true }
);
RequestListener
type RequestListener = (request: Request, next: RequestListener) => Promise<Response>;
To continue the middleware chain, call next(request). To short-circuit, return a Response (or Response-like object) directly.
HttpOptions
Added in: v5.2.0 (name, before, after, urlPath, and host)
| Property | Type | Default | Description |
|---|---|---|---|
name | string | Registering component's name | Name this middleware entry so other entries can position themselves relative to it |
before | string | - | Run this entry before the named middleware entry |
after | string | - | Run this entry after the named middleware entry |
urlPath | string | - | Only handle requests whose pathname matches this prefix on a segment boundary (/api matches /api and /api/x, not /apinews). Harper removes the prefix before passing the request to the handler. |
host | string | - | Only handle requests whose Host header matches this virtual hostname (case-sensitive, port ignored) |
runFirst | boolean | false | Deprecated. Insert this handler at the front of the chain. Use before or after for explicit ordering. |
port | number | http.port | Target the HTTP server on this port |
securePort | number | http.securePort | Target the HTTPS server on this port |
host and urlPath create a routed middleware chain. When both are present, both must match. Harper selects the most specific matching chain in this order: host and path, host only, then path only. Longer path prefixes take precedence. A request that matches no routed chain uses the default chain.
server.http(handleAdminRequest, {
name: 'admin',
host: 'admin.example.com',
urlPath: '/api',
after: 'authentication',
});
For this example, a request to https://admin.example.com/api/users reaches handleAdminRequest with a pathname of /users.
HttpServer
A Node.js http.Server or https.SecureServer instance.
Request
A Request object is passed to HTTP middleware handlers and direct static REST handlers. It follows the WHATWG Request API with additional Harper-specific properties.
Properties
| Property | Type | Description |
|---|---|---|
url | string | The request target (path + query string), e.g. /path?query=string |
method | string | HTTP method: GET, POST, PUT, DELETE, etc. |
headers | Headers | Request headers |
pathname | string | Path portion of the URL, without query string |
protocol | string | http or https. On the Node runtime, behind a TLS-terminating proxy that forwards the PROXY v2 SSL TLV, this reflects the client's original scheme even though the proxy-to-Harper hop is plaintext; a proxy forwarding only address info (no SSL TLV) still reads as http. Bun and uWebSockets derive this from X-Forwarded-Proto/the listener's secure flag instead, independent of the SSL TLV. Changed in: v5.2.0 |
data | any | Deserialized body, based on Content-Type header |
ip | string | Remote IP address of the client (or last proxy) |
host | string | Host from the request headers |
session | object | Current cookie-based session (a Table record instance). Update with request.session.update({ key: value }). A cookie is set automatically the first time a session is updated or a login occurs. |
connectionInfo | object | undefined | TLS facts a fronting proxy forwarded over PROXY protocol v2 — negotiated ALPN, SNI, TLS version/cipher, the client's JA3/JA4 fingerprint, and the client's mTLS certificate chain (presented, not necessarily verified — see below). undefined for a direct connection or one without a v2 header. See Connection info. Added in: v5.2.0 |
Connection info
Added in: v5.2.0When Harper runs behind a TLS-terminating proxy (for example, Harper Fabric's Symphony edge), the TLS handshake happens at the proxy, so facts about the client's TLS connection — its fingerprint, negotiated protocol, and any client certificate — are otherwise invisible to your application. Harper Fabric's proxy-protocol Unix domain socket mirrors decode this from a forwarded PROXY protocol v2 header and expose it on request.connectionInfo; a PROXY v2 header sent to an ordinary http.port/securePort listener isn't decoded (and isn't valid HTTP, so the connection fails).
interface ConnectionInfo {
alpn?: string; // negotiated ALPN protocol, e.g. "h2" — raw, unvalidated
authority?: string; // SNI hostname from the ClientHello — raw, unvalidated
tls?: {
version?: string; // e.g. "TLSv1.3"
cipher?: string; // negotiated cipher suite
verified?: boolean; // a client certificate was presented and the proxy verified it
};
ja3?: string; // JA3 fingerprint (32-char MD5 hex) — raw, unvalidated
ja4?: string; // JA4 fingerprint — raw, unvalidated
clientCertChain?: Buffer[]; // client certificate chain (DER, leaf first) — presented, not necessarily verified; check tls.verified
}
request.connectionInfo is undefined unless a PROXY v2 header carrying TLS facts was decoded; each field is present only when the proxy actually forwarded it. On the Bun and uWebSockets runtimes it is always undefined. alpn, authority, ja3, and ja4 are string-decoded from the TLV payload with no grammar or length validation beyond the header's own bounds, so validate them before placing them in response headers, logs, cache keys, or other security-sensitive contexts. Reading the client's JA4 fingerprint — e.g. to make a bot or abuse decision — is simply:
class Origin {
get(request) {
const ja4 = request.connectionInfo?.ja4;
if (ja4 && abuseList.has(ja4)) return new Response('blocked', { status: 403 });
return fetch(request);
}
}
Trust and mTLS. connectionInfo is populated only from the trusted proxy-protocol channel (Harper Fabric's per-worker Unix domain socket, writable only by the local proxy) — never from a request header, so the channel itself cannot be forged by a client (its contents are client-controlled, per above). clientCertChain and tls.verified are independent signals, not one another's proof: a chain is attached whenever the client presented a certificate, whether or not the proxy verified it, and tls.verified can be true while clientCertChain is absent (a proxy may verify a certificate but omit an oversized chain). Treating chain presence alone as proof of a verified client accepts unverified certificates. For gating on a verified mTLS client, use request.authorized — a chain-gated boolean the proxy layer populates with the same semantics as a directly-terminated TLS connection — rather than connectionInfo.tls.verified or clientCertChain presence alone. Like connectionInfo, this is Node-runtime only: request.authorized is always undefined on Bun and uWebSockets.
Methods
request.login(username, password)
login(username: string, password: string): Promise<void>
Authenticates the user by username and password. On success, creates a session and sets a cookie on the response. Rejects if authentication fails.
request.sendEarlyHints(link, headers?)
sendEarlyHints(link: string, headers?: object): void
Sends an Early Hints (HTTP 103) response before the final response. Useful in cache resolution functions to hint at preloadable resources:
class Origin {
async get(request) {
this.getContext().requestContext.sendEarlyHints('<link rel="preload" href="/my-resource" as="fetch">');
return fetch(request);
}
}
Cache.sourcedFrom(Origin);
Low-Level Node.js Access
These properties expose the raw Node.js request/response objects and should be used with caution. Using them can break other middleware handlers that depend on the layered Request/Response pattern.
| Property | Description |
|---|---|
_nodeRequest | Underlying http.IncomingMessage |
_nodeResponse | Underlying http.ServerResponse |
Response
REST method handlers can return:
- Data directly — Serialized using Harper's content negotiation
- A
Responseobject — The WHATWGResponse - A
Response-like object — A plain object with the following properties:
| Property | Type | Description |
|---|---|---|
status | number | HTTP status code (e.g. 200, 404) |
headers | Headers | Response headers |
data | any | Response data, serialized via content negotiation |
body | Buffer | string | ReadableStream | Blob | Raw response body (alternative to data) |
server.ws(listener, options)
Add a handler to the WebSocket connection middleware chain.
server.ws(listener: WsListener, options?: WsOptions): HttpServer[]
Example:
server.ws((ws, request, chainCompletion) => {
chainCompletion.then(() => {
ws.on('message', (data) => console.log('received:', data));
ws.send('hello');
});
});
WsListener
type WsListener = (ws: WebSocket, request: Request, chainCompletion: Promise<void>, next: WsListener) => Promise<void>;
| Parameter | Description |
|---|---|
ws | WebSocket instance |
request | Harper Request object from the upgrade event |
chainCompletion | Promise that resolves when the HTTP request chain finishes. Await before sending to ensure the HTTP request is handled. |
next | Continue chain: next(ws, request, chainCompletion) |
WsOptions
WsOptions supports the same options as HttpOptions, plus:
| Property | Type | Default | Description |
|---|---|---|---|
maxPayload | number | 100 MB | Maximum WebSocket message size accepted by ws |
server.upgrade(listener, options)
Add a handler to the HTTP server upgrade event. Use this to delegate upgrade events to an external WebSocket server.
server.upgrade(listener: UpgradeListener, options?: UpgradeOptions): void
Example (from the Harper Next.js component):
server.upgrade(
(request, socket, head, next) => {
if (request.url === '/_next/webpack-hmr') {
return upgradeHandler(request, socket, head).then(() => {
request.__harperRequestUpgraded = true;
next(request, socket, head);
});
}
return next(request, socket, head);
},
{ runFirst: true }
);
When server.ws() is registered, Harper adds a default upgrade handler. The default handler sets request.__harperRequestUpgraded = true after upgrading, and checks for this flag before upgrading again (so external upgrade handlers can detect whether Harper has already handled the upgrade).
UpgradeListener
type UpgradeListener = (request: IncomingMessage, socket: Socket, head: Buffer, next: UpgradeListener) => void;
UpgradeOptions
UpgradeOptions supports the same options as HttpOptions.
server.socket(listener, options)
Create a raw TCP or TLS socket server.
server.socket(listener: ConnectionListener, options: SocketOptions): SocketServer
Only one socket server is created per call. A securePort takes precedence over port.
ConnectionListener
Node.js connection listener as in net.createServer or tls.createServer.
SocketOptions
| Property | Type | Description |
|---|---|---|
port | number | Port for a net.Server |
securePort | number | Port for a tls.Server |
SocketServer
A Node.js net.Server or tls.Server instance.
server.authenticateUser(username, password)
Added in: v4.5.0
server.authenticateUser(username: string, password: string): Promise<User>
Returns the user object for the given username after verifying the password. Throws if the password is incorrect.
Use this when you need to explicitly verify a user's credentials (e.g., in a custom login endpoint). For lookup without password verification, use server.getUser().
server.getUser(username)
server.getUser(username: string): Promise<User>
Returns the user object for the given username without verifying credentials. Use for authorization checks when the user is already authenticated.
server.resources
The central registry of all resources exported for REST, MQTT, and other protocols.
server.resources.set(name, resource, exportTypes?)
Register a resource:
class NewResource extends Resource {}
server.resources.set('NewResource', NewResource);
// Limit to specific protocols:
server.resources.set('NewResource', NewResource, { rest: true, mqtt: false });
server.resources.getMatch(path, exportType?)
Find a resource matching a path:
server.resources.getMatch('/NewResource/some-id');
server.resources.getMatch('/NewResource/some-id', 'rest');
server.operation(operation, context?, authorize?)
Execute an Operations API operation programmatically.
server.operation(operation: object, context?: { username: string }, authorize?: boolean): Promise<any>
| Parameter | Type | Description |
|---|---|---|
operation | object | Operations API request body |
context | { username: string } | Optional: execute as this user |
authorize | boolean | Whether to apply authorization. Defaults to false. |
server.recordAnalytics(value, metric, path?, method?, type?)
Record a metric into Harper's analytics system.
server.recordAnalytics(value: number, metric: string, path?: string, method?: string, type?: string): void
| Parameter | Description |
|---|---|
value | Numeric value (e.g. duration in ms, bytes) |
metric | Metric name |
path | Optional URL path for grouping (omit per-record IDs — use the resource name) |
method | Optional HTTP method for grouping |
type | Optional type for grouping |
Metrics are aggregated and available via the analytics API.
server.config
The parsed harper-config.yaml configuration object. Read-only access to Harper's current runtime configuration.
server.nodes
Returns an array of node objects registered in the cluster.
server.shards
Returns a map of shard number to an array of associated nodes.
server.hostname
Returns the hostname of the current node.
server.contentTypes
Returns the Map of registered content type handlers. Same as the global contentTypes object.
contentTypes
A Map of content type handlers for HTTP request/response serialization. Harper uses content negotiation: the Content-Type header selects the deserializer for incoming requests, and the Accept header selects the serializer for responses.
Built-in Content Types
| MIME type | Description |
|---|---|
application/json | JSON |
application/cbor | CBOR |
application/msgpack | MessagePack |
text/csv | CSV |
text/event-stream | Server-Sent Events |
Custom Content Type Handlers
Register or replace a handler by setting it on the contentTypes map:
import { contentTypes } from 'harper';
contentTypes.set('text/xml', {
serialize(data) {
return '<root>' + serialize(data) + '</root>';
},
q: 0.8, // quality: lower = less preferred during content negotiation
});
Handler Interface
| Property | Type | Description |
|---|---|---|
serialize(data) | (any) => Buffer | Uint8Array | string | Serialize data for a response |
serializeStream(data) | (any) => ReadableStream | Serialize as a stream (for async iterables or large data) |
deserialize(buffer) | (Buffer | string) => any | Deserialize an incoming request body. Used when deserializeStream is absent. String for text/* types, Buffer for binary types. |
deserializeStream(stream) | (ReadableStream) => any | Deserialize an incoming request stream |
q | number (0–1) | Quality indicator for content negotiation. Defaults to 1. |