Skip to content

HTTP Handlers

SpacetimeDB: HTTP Handlers ↗

HTTP routes are server-to-server routes, not caller-authenticated reducers. Their context exposes the module database identity, timestamp, HTTP request data, randomness, UUID helpers, and transaction-scoped DB access. It does not expose a caller sender, caller identity, or connection id.

Declare routes in an StdbHttpGroup. Typed routes provide request and response schemas; raw routes omit them and work directly with Request and SyncResponse.

import * as Effect from "effect/Effect"
import * as Stdb from "effect-spacetimedb"
const tokenRotation = Stdb.table("token_rotation", {
columns: {
userId: Stdb.string().primaryKey(),
token: Stdb.string(),
},
})
const RotateTokenInput = Stdb.struct({ userId: Stdb.string() }).schema
const RotateTokenOutput = Stdb.struct({ token: Stdb.string() }).schema
const Webhooks = Stdb.StdbHttpGroup.make("Webhooks")
.prefix("/webhooks")
.add(Stdb.StdbHttp.post("stripe_webhook", "/stripe"))
.add(
Stdb.StdbHttp.post("rotate_token", "/rotate", {
request: RotateTokenInput,
response: RotateTokenOutput,
}),
)
const Module = Stdb.StdbModule.make("app")
.addTables(tokenRotation)
.add(Webhooks)
const { HttpTx, Db } = Module
const WebhooksLive = Stdb.StdbBuilder.group(Module, "Webhooks", {
stripe_webhook: (request) =>
Effect.succeed(new Stdb.SyncResponse(request.text(), { status: 202 })),
rotate_token: (args) =>
Effect.gen(function* () {
const tx = yield* HttpTx
return yield* tx.run(
Effect.gen(function* () {
const db = yield* Db
const row = yield* db.token_rotation.userId.replace({
userId: args.userId,
token: `token:${args.userId}`,
})
return { token: row.token }
}),
)
}),
})

Use raw routes for GET or HEAD, query strings, custom auth headers, webhooks, and non-JSON bodies. Keep external side effects outside HTTP transaction bodies, just as you would for procedures.

effect-spacetimedb uses StdbHttpGroup plus StdbBuilder.group(...) instead of exporting handler functions directly. Typed post, put, and patch routes can also be projected to an Effect HttpApi.

Typed post, put, and patch routes can also be projected into a canonical Effect HttpApi. The STDB HTTP group becomes the HttpApiClient property key, and the route name becomes the typed endpoint method.

import * as Effect from "effect/Effect"
import * as HttpClient from "effect/unstable/http/HttpClient"
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient"
import * as Stdb from "effect-spacetimedb"
declare const uri: string
declare const databaseName: string
declare const token: string
declare const userId: string
const RotateTokenInput = Stdb.struct({ userId: Stdb.string() }).schema
const RotateTokenOutput = Stdb.struct({ token: Stdb.string() }).schema
const Webhooks = Stdb.StdbHttpGroup.make("Webhooks").add(
Stdb.StdbHttp.post("rotate_token", "/rotate", {
request: RotateTokenInput,
response: RotateTokenOutput,
}),
)
const Module = Stdb.StdbModule.make("app").add(Webhooks)
const api = Stdb.toHttpApi(Module.spec)
const program = Effect.gen(function* () {
const client = yield* HttpApiClient.make(api, {
baseUrl: Stdb.httpApiBaseUrl({ uri, databaseName }),
transformClient: HttpClient.mapRequest(
HttpClientRequest.bearerToken(token),
),
})
const response = yield* client.Webhooks.rotate_token({
payload: { userId },
})
return response.token
})