@noverax/courier-sveltekit serves the proxy that createProxyCaller and createProxyQueryCaller call: one route answers every endpoint, reads over GET and writes over POST, and streams server-sent events.
In one command
npx @noverax/create-courierIn a SvelteKit app it adds what this page sets up by hand — the endpoint, the registration in hooks.server.ts, and Studio's plugin — with a typed example API, its mock, and a page at /courier. See Create Courier.
Install
npm install @noverax/courier-sveltekit @noverax/courier @sveltejs/kitUsage
// src/hooks.server.ts
import { registerSources } from "@noverax/courier"
import { sources } from "$lib/api"
registerSources(sources)// src/routes/api/request/+server.ts
import { courierHandler } from "@noverax/courier-sveltekit"
const handler = courierHandler()
export const GET = handler
export const POST = handlercourierHandler() is zero-arg — sources are read at request time from the core global registry populated by registerSources() in hooks.server.ts. The handler uses Web Standard Request/Response natively. GET requests are routed to query-type endpoints, POST requests handle mutations.
In production
SvelteKit answers some calls before the proxy sees them. With @sveltejs/adapter-node:
- Set
ORIGINto the URL the app is served at, such asORIGIN=https://app.example.com node build. Without it SvelteKit cannot tell the protocol it is served over and reports anhttps:request URL, which a browser'shttp:origin never matches, so the proxy refuses everyPOSTwith 403. Behind a proxy that sets forwarding headers,PROTOCOL_HEADERandHOST_HEADERdo the same job. - Raise
BODY_SIZE_LIMIT, which is 512 KB by default and answers any larger body with its own 413Payload Too Large— a 900 KB JSON call or any upload over it — whatever the proxy allows. The proxy reads a JSON call up to itsmaxBodyBytes, 1 MB, and an upload up to itsmaxUploadBytes, 10 MB;BODY_SIZE_LIMIT=11Mcovers both, and a largermaxUploadBytesneeds a larger limit. - Callers without an
Origin: SvelteKit's CSRF check refuses amultipart/form-dataortext/plainPOSTwhoseOrigindoes not match, and one with noOriginat all, such as a server-side script, with a plain-text 403. Browser pages on the app's own origin pass it.
In vite dev, a chunked body larger than the limit resets the connection instead of answering 413, and a client that declares an oversized upload can see the connection close before it has sent it all.
The client
Pair with createProxyCaller on the client:
import { createProxyCaller } from "@noverax/courier"
import type { api } from "./api"
// The first argument is the key this source has in your `sources` map —
// it is what the proxy addresses, and it travels as `?_s=<name>`.
const caller = createProxyCaller<typeof api>("api", {
proxyPath: { http: "/api/request" },
})
const users = await caller.users.getAll()The user's session
An API that keeps a session gets the user's session with every call a hook, a load function, a form action or an endpoint makes, once the request being served can be found — and a proxy caller in universal code, rendered on the server, reaches the proxy in process with it:
import { getRequestEvent } from "$app/server"
import { registerRequestContext, registerSources } from "@noverax/courier"
import { svelteKitRequestContext } from "@noverax/courier-sveltekit"
import { sources } from "$lib/api"
registerSources(sources)
registerRequestContext(svelteKitRequestContext(getRequestEvent))import { requestContextHandle } from "@noverax/courier-sveltekit/handle"
export const handle = requestContextHandle()getRequestEvent follows the request through every await where the platform has AsyncLocalStorage, and only until the first one where it has none. The handle hook needs AsyncLocalStorage itself — Node, Bun, Deno, Cloudflare with nodejs_als. Either way the session cookies the API sets go to cookies.set() exactly as the API wrote them, a load function's included. See User Sessions.
Cancelled calls
When the browser cancels a call — an aborted upload, a query it no longer needs — the proxy stops its own upstream request. SvelteKit's request signal does not report a client that leaves after its body has arrived, so under @sveltejs/adapter-node the handler watches the client's socket, from platform.req, instead. The Vite dev server exposes no connection: during vite dev a cancelled call still runs to completion upstream.