HTTP Request and Response Parameter Mapping (Effect)
Effect Golem endpoints declare request bindings with the Http namespace and describe every
method parameter and result with Effect Schema. The Golem host performs the HTTP parsing and
response rendering; handlers receive typed values and return Effect values rather than reading
or constructing raw HTTP requests and responses.
Load golem-add-http-endpoint-effect when the agent still needs a mount, endpoint declarations,
an implementation import in src/main.ts, or an httpApi deployment.
Path Variables
Place constructor identity fields in the mount path and method fields in endpoint paths. Variable
names must exactly match the corresponding id or input keys, including
TypeScript casing:
import { Schema } from "effect";
import { defineAgent, Http, method } from "@golemcloud/effect-golem";
export const TaskAgent = defineAgent({
name: "TaskAgent",
mode: "durable",
id: {
taskName: Schema.String,
},
http: Http.mount("/api/tasks/{taskName}"),
methods: {
getItem: method({
input: { itemId: Schema.String },
success: Item,
http: [Http.get("/items/{itemId}")],
}),
},
});
Every agent id field must appear in the mount path. This Effect SDK has no API for binding a mount header to a agent id field. Mount paths also cannot contain query parameters or catch-all variables.
An endpoint catch-all captures the remaining path and must be its final segment:
serveFile: method({
input: { path: Schema.String },
success: FileContent,
http: [Http.get("/files/{*path}")],
}),
// GET .../files/docs/readme.md supplies "docs/readme.md" as path.
Query Parameters
Declare query bindings in the endpoint path with key={parameterName}. The HTTP key may differ
from the TypeScript parameter name:
search: method({
input: {
query: Schema.String,
minPrice: Schema.NullOr(Schema.Number),
inStockOnly: Schema.NullOr(Schema.Boolean),
},
success: Schema.Array(Product),
http: [
Http.get(
"/products/search?q={query}&min-price={minPrice}&in-stock-only={inStockOnly}",
),
],
}),
For query and header parameters, Schema.NullOr(T) represents an optional value: an omitted value
is supplied as null. Keep path variables non-optional because a matching path always contains
the segment.
Header Variables
The endpoint options map each HTTP header name to one method parameter name:
submitReport: method({
input: {
tenantId: Schema.String,
report: Report,
},
success: Receipt,
http: [
Http.post("/reports", {
headers: { "X-Tenant": "tenantId" } as const,
}),
],
}),
Here tenantId comes from X-Tenant, while the unbound report parameter comes from the JSON
body field named report. Header names are case-insensitive for duplicate detection. Query keys
and all parameter names are case-sensitive.
A parameter may be bound only once within an endpoint. Do not bind the same parameter from two path positions, two query keys, two headers, or a mixture of those sources.
Schemas Allowed in Path, Query, and Headers
Route-bound values must use a string-bindable schema:
| Effect Schema | Parsed value |
|---|---|
Schema.String |
string |
Schema.Number |
JavaScript number / WIT f64 |
Schema.Boolean |
boolean |
Schema.BigInt |
bigint / WIT s64 |
Schema.Literal(...) or a union of scalar literals |
validated literal value |
| scalar template literal, refinement, brand, or transformation | decoded scalar value |
For a string enum, use the Effect v4 union form:
const SortOrder = Schema.Union([
Schema.Literal("ascending"),
Schema.Literal("descending"),
]);
Wrap a scalar with Schema.NullOr(...) only for an optional query or header. Structs, tuples,
records, unstructured values, multimodal values, nested collections, and non-empty arrays are not
route-bindable.
Query and header bindings additionally accept Schema.Array(T) when T is one of the supported
primitive, literal/string-enum, template-literal, refinement, brand, or transformation schemas
listed above. The host collects repeated query instances and repeated raw header fields in arrival
order:
collectionBindings: method({
input: {
tags: Schema.Array(Schema.String),
scores: Schema.Array(Schema.Number),
},
success: Schema.Struct({
tags: Schema.Array(Schema.String),
scores: Schema.Array(Schema.Number),
}),
http: [
Http.get("/bindings/collections?tag={tags}", {
headers: { "X-Score": "scores" } as const,
}),
],
}),
For example, ?tag=a&tag=b binds tags as ["a", "b"]; three raw X-Score fields bind three
numbers. A single comma-separated header line is one value and is not split into collection items.
Path variables remain scalar-only.
JSON Body Mapping
Use Http.post, Http.put, Http.del, Http.patch, or another bodyful helper when parameters
must come from a body. Every parameter not bound to a path, query key, or header becomes a
top-level field in the JSON object, using the exact TypeScript parameter name:
updateItem: method({
input: {
id: Schema.String,
name: Schema.String,
count: Schema.Number,
},
success: Item,
http: [Http.put("/items/{id}")],
}),
The request body is:
{ "name": "Widget", "count": 5 }
The body is always an object keyed by method parameter name. A single unbound parameter
decision: Schema.String expects { "decision": "approved" }, not the bare JSON string
"approved". Likewise, input: { item: Item } expects { "item": { ... } }; the fields of the
Item struct are not flattened into the top-level body.
Http.get and Http.head are bodyless, so every parameter of those methods must be bound from
the path, query, or headers. Prefer the standard verb helpers over Http.custom(...) for standard
methods so the SDK can apply its bodyless validation.
Structured Data and JSON
Use schemas that the SDK can lower to WIT:
| Effect Schema | JSON representation |
|---|---|
Schema.String |
string |
Schema.Number |
number |
Schema.Boolean |
boolean |
Schema.Array(T) |
array |
Schema.Struct({ ... }) |
object with the declared field names |
Schema.Tuple([...]) |
array |
Schema.NullOr(T) |
value or null |
| union of string literals | string enum value |
| tagged union | tagged variant object |
Use Schema.Struct for fixed object fields. Open-ended Schema.Record(...) index signatures are
not supported by the pinned Effect SDK's WIT schema compiler. Keep transformations and refinements
serializable through their encoded schemas.
HTTP Response Mapping
Declare the HTTP outcome through the method's success and optional error schemas:
| Method contract and handler result | HTTP response |
|---|---|
success: Schema.Void, return Effect.void |
204, empty body |
success: T, return Effect<T> |
200, JSON T |
success: UnstructuredText(...), return inline text |
200, plain text |
success: UnstructuredBinary(...), return inline bytes |
200, raw bytes |
success: Schema.NullOr(T), return T |
200, JSON T |
success: Schema.NullOr(T), return null |
404, empty body |
declare error: E, return Effect.fail(E) |
500, JSON E |
Use Schema.NullOr(T) for ordinary not-found behavior. Use a declared error schema and
Effect.fail(...) for expected typed failures. Defects such as Effect.die(...) are unexpected
invocation failures and may be retried; do not use them to select an HTTP status.
The SDK does not expose a raw response builder or per-error status mapping. Do not return an
Effect Platform HttpServerResponse or invent response-header/status helpers.
For plain text or binary output, set method success to
Unstructured.UnstructuredText(...) or Unstructured.UnstructuredBinary(...) and return an
inline TextReferenceValue or BinaryReferenceValue. Inline text uses text/plain plus its
optional language metadata; inline binary uses the value's declared MIME type. Unstructured
success values cannot be combined with a typed method error. Load
golem-add-http-endpoint-effect for complete value examples.
Malformed Bound Input
The host rejects malformed route-bound input before invoking the handler. The response is 400 Bad Request, has a JSON content type, and has this stable shape:
{ "code": "REQUEST_VALUE_PARSING_FAILED", "errors": ["descriptive message"] }
The stable codes distinguish common cases:
| Case | Code |
|---|---|
| Missing required query or header value | REQUEST_MISSING_VALUE |
| Malformed scalar or unparsable collection item | REQUEST_VALUE_PARSING_FAILED |
| More than one value for a scalar binding | REQUEST_TOO_MANY_VALUES |
Do not duplicate this parsing in the handler or replace these host responses with custom response construction.
Unstructured Request Bodies
The Effect SDK exposes top-level unstructured method parameters under the Unstructured
namespace:
import { Effect, Schema } from "effect";
import { Http, method, Unstructured } from "@golemcloud/effect-golem";
const upload = method({
input: {
payload: Unstructured.UnstructuredBinary({
restrictions: [{ mimeType: "image/png" }],
}),
},
success: Schema.Number,
http: [Http.post("/upload")],
});
// In the implementation:
const uploadHandler = ({
payload,
}: {
payload: Unstructured.BinaryReferenceValue;
}) =>
Effect.succeed(payload._tag === "inline" ? payload.val.data.byteLength : -1);
Use Unstructured.UnstructuredText({ restrictions: [{ languageCode: "en" }] }) for a restricted
text input, or omit restrictions to accept any declared text language or binary MIME type. An
unstructured value cannot bind to path, query, or headers; keep it as the only body parameter.
method.success accepts either an ordinary Effect Schema or one top-level
UnstructuredBinary(...) / UnstructuredText(...) element specification. Unstructured values
cannot be nested inside Schema.Struct or combined into a multimodal success value.
Authenticated Principal
The authenticated caller is an Effect Context service, not a method parameter or HTTP binding:
import { Effect } from "effect";
import { Principal } from "@golemcloud/effect-golem";
const currentCaller = Effect.gen(function* () {
return yield* Principal.Principal;
});
Enable authentication with Http.mount(path, { auth: true }) or an endpoint auth option, then
read Principal.Principal inside the handler for the current invocation. Load
golem-add-http-auth-effect for deployment security configuration and principal variants.
Key Constraints
- Import Effect APIs from
effectand Golem APIs from@golemcloud/effect-golem. - Match every placeholder and header target to an exact
inputoridkey. - Use
Schema.NullOr(...)andnullfor optional query/header values and 404 success results. - Keep GET/HEAD parameters fully bound and bodyful request bodies as named JSON objects.
- Return Effects from handlers; do not use plain values,
asynchandlers, or raw HTTP middleware. - Do not use
@golemcloud/golem-ts-sdkdecorators or invent APIs from another language SDK.