The @noverax/courier-fastify adapter provides a Fastify plugin that registers the Courier proxy route.
In one command
npx @noverax/create-courierIn a Fastify server it adds a plugin with the proxy route and a page at /courier — loaded by itself in an app fastify-cli made — with a typed example API and its mock. See Create Courier.
Installation
npm install @noverax/courier @noverax/courier-fastify fastify @fastify/corsDefine your API sources
import { courier, defineApi, defineGroup } from "@noverax/courier/define"
const { endpoint } = courier()
type User = { id: number; name: string; email: string }
const users = defineGroup({
prefix: "users",
endpoints: {
getAll: endpoint.get("").response<User[]>(),
getById: endpoint.get(":id").variables<{ id: number }>().response<User>(),
create: endpoint.post("").variables<{ name: string; email: string }>().response<User>(),
remove: endpoint.del(":id").variables<{ id: number }>().response<null>(),
},
})
export const api = defineApi({
baseURL: process.env.API_URL ?? "http://localhost:4000",
groups: { users },
})
export const sources = { myApi: api }Setup
Create a Fastify server with the Courier plugin:
import Fastify from "fastify"
import cors from "@fastify/cors"
import { registerSources } from "@noverax/courier"
import { courierPlugin } from "@noverax/courier-fastify"
import { sources } from "./lib/sources"
registerSources(sources)
const app = Fastify({ logger: true })
async function start() {
await app.register(cors, {
origin: "http://localhost:5173",
credentials: true,
})
// Register the Courier proxy plugin
await app.register(courierPlugin, {
prefix: "/api/request",
})
await app.listen({ port: 3001 })
}
start()The courierPlugin registers a route handler at the specified prefix that reads sources from the core global registry populated by registerSources() at module load.
With existing Fastify app
Add the Courier plugin alongside your existing routes and plugins:
import Fastify from "fastify"
import cors from "@fastify/cors"
import { registerSources } from "@noverax/courier"
import { courierPlugin } from "@noverax/courier-fastify"
import { sources } from "./lib/sources"
registerSources(sources)
const app = Fastify({ logger: true })
async function start() {
await app.register(cors, {
origin: "http://localhost:5173",
credentials: true,
})
// Your existing routes
app.get("/health", async () => {
return { status: "ok" }
})
app.get("/api/stats", async () => {
return { uptime: process.uptime() }
})
// Register Courier proxy plugin
await app.register(courierPlugin, {
prefix: "/api/request",
})
await app.listen({ port: 3001 })
}
start()Plugin encapsulation
The courierPlugin follows Fastify's plugin system. It encapsulates its routes under the given prefix without interfering with your other routes or plugins.
CORS configuration
For production, restrict the allowed origins:
import Fastify from "fastify"
import cors from "@fastify/cors"
import { registerSources } from "@noverax/courier"
import { courierPlugin } from "@noverax/courier-fastify"
import { sources } from "./lib/sources"
registerSources(sources)
const app = Fastify({ logger: true })
async function start() {
await app.register(cors, {
origin: process.env.ALLOWED_ORIGINS?.split(",") ?? "http://localhost:5173",
credentials: true,
})
await app.register(courierPlugin, {
prefix: "/api/request",
})
await app.listen({
port: Number(process.env.PORT) || 3001,
host: "0.0.0.0",
})
}
start()Fastify performance
Fastify's schema-based validation and serialization make it one of the fastest Node.js frameworks. The Courier plugin benefits from Fastify's built-in JSON parsing, so no additional body parser setup is needed.
The user's session
An API that keeps a session gets the user's session with every call a route, a service or a step it awaits makes, once the request is served with it at hand:
import { requestContext } from "@noverax/courier-fastify"
app.addHook("onRequest", requestContext())Add the hook on the root instance, where it runs for every route whichever plugin registers it. The session cookies the API sets reach the browser on the reply, beside any your route sets, and the user's address follows the rule the proxy route follows. Fastify 4 and 5 are checked. See User Sessions.
Studio integration & appOrigin
Wire your Fastify server into Studio with a single attachRuntime() call. In development it loads courier.config.ts from disk, registers its sources, and opens the connection to Studio; in production the app's own registerSources() call registers them. The appOrigin field for Playground proxy SSE/WS resolution lives in courier.config.ts.runtime.appOrigin (or set the COURIER_APP_URL env var to override per-environment).
import Fastify from "fastify"
import { courierPlugin } from "@noverax/courier-fastify"
const app = Fastify()
async function start() {
if (process.env.NODE_ENV !== "production") {
const { attachRuntime } = await import("@noverax/courier-studio-core/runtime")
await attachRuntime()
}
await app.register(courierPlugin, { prefix: "/api/request" })
await app.listen({ port: 3001 })
}
start()Alternatively, set COURIER_APP_URL instead of passing appOrigin explicitly:
COURIER_APP_URL=http://localhost:3001 node server.jsNo magic default
There is no localhost:3001 auto-fallback. If appOrigin is missing, clicking "Subscribe" on an SSE/WS endpoint in Studio Playground proxy mode throws ConfigurationError("APP_ORIGIN_NOT_CONFIGURED") with a pointer back to this section. The correct value depends on your runtime — custom port, Docker host, HTTPS, reverse proxy — and a wrong guess would silently route requests to nowhere.
Client-side setup
On the client, point your caller to this server's proxy URL. Choose the guide for your frontend:
- React (Vite / CRA) —
createProxyQueryCallerwith React Query hooks - Vue —
createProxyCallerwith composables - Next.js — full-stack with
apiRouteandcreateProxyQueryCaller - Nuxt — full-stack with
courierHandlerandcreateProxyCaller
import { createProxyCaller } from "@noverax/courier"
import type { api } from "../shared/sources"
export const caller = createProxyCaller<typeof api>("api", {
proxyPath: { http: "http://localhost:3001/api/request" },
})