Using HTTP routers and files
Use an HTTP router to handle streaming requests, serve static assets, or integrate an HTTP framework. To publish files created by a durable agent, add live file mappings to that agent instead. The router and file semantics page explains how these features interact with code-first endpoints, authentication, and durable execution.
The examples below belong in an existing Golem application. Use a CLI and SDK from the same Golem version. Commands assume a local server on your machine, with the application gateway on port 9006.
Handling streaming requests
Define a router with a unique type name and a literal mount path. This EchoRouter
streams an upload back to the caller at /echo.
It does not collect the upload into memory or calculate its length first.
TypeScript
import { defineHttpRouter } from '@golemcloud/golem-ts-sdk';
export const EchoRouter = defineHttpRouter('EchoRouter')
.mount('/echo')
.implement((request) => new Response(request.body));The Web adapter accepts standard Request and Response objects, so a framework
with a Fetch-compatible handler can supply the function passed to .implement.
Keep its body streaming. For the original, unnormalized HTTP envelope, use
.implementRaw instead; see Working with headers.
Web Request, Response, and Headers are normalized views, not a lossless
replacement for the canonical envelope. Web APIs can normalize methods and URLs,
combine fields, or reject methods and statuses. The Web handler’s context exposes
the original request; withRawHeaders supplies ordered byte-valued response fields.
Merge this deployment into your application’s manifest:
httpApi:
deployments:
local:
- domain: localhost:9006
scheme: http
agents:
EchoRouter: {}Build, deploy, and send an upload:
golem build -L
golem deploy -L -Y
printf 'stream me' | curl --data-binary @- http://localhost:9006/echoThe response is stream me. For a router with several routes, dispatch on the
request’s method and path, and return 404 for unknown routes. The canonical request
uses the full public path; framework adapters may present mount-relative paths
as noted in their tabs. Routing precedence
explains when a code-first endpoint or file mapping takes priority over the handler.
Working with headers
Keep repeated headers separate, especially Set-Cookie. The raw APIs use lowercase
names and byte-valued fields. For example, add two cookies to a streaming response:
TypeScript
The Web adapter already preserves separate Set-Cookie fields. For byte-valued
or strictly ordered fields, import withRawHeaders from @golemcloud/golem-ts-sdk
and replace the handler with:
.implement((request) => withRawHeaders(new Response(request.body), [
{ name: 'set-cookie', value: new TextEncoder().encode('first=one') },
{ name: 'set-cookie', value: new TextEncoder().encode('second=two') },
]));withRawHeaders replaces all response headers with this list, rather than
adding to Response.headers. Include every header you want emitted.
For canonical methods, paths, queries, and headers without Web API normalization,
replace .implement(...) with a raw handler:
.implementRaw((request) => ({
status: 200,
headers: [
{ name: 'set-cookie', value: new TextEncoder().encode('first=one') },
{ name: 'set-cookie', value: new TextEncoder().encode('second=two') },
],
body: request.body,
}));A Web handler can also read context.rawRequest from its second argument to inspect
the original head. The body still has only one reader.
Let Golem handle transfer framing. Omit Content-Length unless you know the exact
emitted byte count. Release unused input and stop producing when the output consumer
closes. Do not turn stream errors into normal EOF. See the
streaming contract for
bodyless responses and the distinction between EOF and invocation success.
Serving static files
Create a router with file mappings and no handler. This StaticRouter serves an
explicit index at /static and assets below /static/assets/:
TypeScript
import { defineHttpRouter } from '@golemcloud/golem-ts-sdk';
export const StaticRouter = defineHttpRouter('StaticRouter')
.mount('/static')
.static('/', '/public/index.html')
.static('/assets/*', '/public/assets/$1')
.implement();Create index.html and message.txt beside golem.yaml. Provision them as
read-only files under the router’s type name, and add it to the HTTP deployment:
agents:
StaticRouter:
files:
- sourcePath: ./index.html
targetPath: /public/index.html
permissions: read-only
- sourcePath: ./message.txt
targetPath: /public/assets/message.txt
permissions: read-only
httpApi:
deployments:
local:
- domain: localhost:9006
scheme: http
agents:
EchoRouter: {}
StaticRouter: {}Redeploy and check the index, metadata, and a byte range:
golem deploy -L -Y
curl http://localhost:9006/static
curl -I http://localhost:9006/static/assets/message.txt
curl -i -H 'Range: bytes=0-3' http://localhost:9006/static/assets/message.txtUse a nonempty message.txt for the range example. Static responses include a
strong ETag and Cache-Control: no-cache. Send the returned ETag in If-None-Match
to revalidate; an unchanged asset returns 304. See the
file HTTP reference for HEAD,
preconditions, and range edge cases.
You can add the same mappings to a router with a handler: matching GET/HEAD files
are served first, and an absent file falls through to the handler. For an override
directory, declare two /assets/* mappings, first to /overrides/$1, then to
/public/assets/$1. Order matters; see
ordered mappings.
Expose only directories intended for HTTP access. There is no implicit index lookup, directory listing, or symlink support. Keep secrets outside exposed roots and protect private mounts with authentication.
Serving live agent files
Use a normal durable agent when files are created or modified at runtime. Bind its
constructor parameters in the mount and expose only the desired files. This
FileOwner creates /value.txt during initialization and overwrites it on update:
TypeScript
import { defineAgent, http, method } from '@golemcloud/golem-ts-sdk';
import * as fs from 'node:fs';
import { z } from 'zod';
export const FileOwner = defineAgent({
name: 'FileOwner',
id: { name: z.string() },
http: http.mount('/files/{name}', {
exposeFiles: [{ route: '/value', path: '/value.txt' }],
}),
methods: { update: method({ input: {}, returns: z.string() }) },
});
export const FileOwnerImpl = FileOwner.implement({
init({ id }) {
fs.writeFileSync('/value.txt', `initial:${id.name}`);
return {};
},
methods: {
update() {
fs.writeFileSync('/value.txt', 'updated');
return 'updated';
},
},
});Add FileOwner: {} beside the routers in the deployment’s agents map, then:
golem deploy -L -Y
curl http://localhost:9006/files/alice/value
golem agent invoke -L 'FileOwner("alice")' update
curl http://localhost:9006/files/alice/valueThe first read returns initial:alice; the second returns updated. Reads use
Cache-Control: no-store with no ETag. No exported method is called to serve the
file, but the first read can initialize the agent. A slow download can delay other
work on that agent; see live-file lifecycle.
Calling an agent from a router
Call ordinary agents through the same clients used for agent-to-agent communication. For example, inside an async handler, update the file owner before constructing the response:
TypeScript
await FileOwner.client.get({ name: 'from-router' }).update();For cross-component calls, declare the dependency and generate its client as described in Calling an agent in another component. A router’s cancellation does not roll back completed agent calls. Client retries are new requests and can repeat effects; see cancellation and recovery.
Publishing OpenAPI
Add an OpenAPI provider to describe your router’s operations. For EchoRouter,
describe POST / relative to its mount; Golem publishes it as POST /echo.
The provider must return OpenAPI 3.1.0, not an OpenAPI 3.0 or YAML document.
TypeScript
Insert this before .implement(...); the SDK serializes the returned object:
.openApi(() => ({
openapi: '3.1.0',
info: { title: 'Echo API', version: '1' },
paths: {
'/': {
post: {
operationId: 'echo',
responses: { '200': { description: 'Streamed request bytes' } },
},
},
},
}))Keep providers finite and deterministic. Use unique component names and operation IDs across the domain, and local references that resolve within the provider’s own document. See OpenAPI generation for the supported subset, security merging, conflicts, caching, and size limits.
After redeployment, retrieve both formats:
golem deploy -L -Y
curl http://localhost:9006/openapi.json
curl http://localhost:9006/openapi.yamlTo place them under /docs, add openapiEndpoint: /docs beside domain and scheme
in the HTTP deployment. The URLs become /docs/openapi.json and /docs/openapi.yaml.
Set scheme to match the public origin. Built-in local environments default to
http, cloud environments to https; custom-server environments must set it explicitly.
OpenAPI endpoints are public even when a router requires authentication. Do not include secret values, private examples, or requester-specific information. Apply a separate ingress policy if the description must be private.
Protecting mounts
Configure authentication and CORS on the router mount, not its handler method. Use the same security scheme configuration as code-first endpoints. The router declarations accept these options:
TypeScript
.mount('/echo', { auth: true, cors: ['https://app.example.com'] })Protect each child mount explicitly: it does not inherit authentication from a parent. Test denial before checking file existence, and test browser preflight separately from plain OPTIONS. See authentication and failures for host behavior and status codes.
Configuring self-hosted deployments
See HTTP Gateway for shared storage, ingress, HTTP API and MCP ports, streaming timeouts, and resource budgets. These are platform operator settings, not application router declarations.
Troubleshooting
See Troubleshooting for routing, file serving, and OpenAPI failures.
Complete examples are available in the repository for TypeScript , Rust , Scala , MoonBit , and Effect .