CORS for a SPA

Reflect allowed origins, enable credentials, and list custom headers the browser will send.

Prerequisites

  • Front-end on a different origin than the API (e.g. Vite on :5173)
  • Decide whether you need cookies / credentialed fetch

Server

import { HttpServer } from "jsr:@webtools/expressapi";

const allowed = new Set([
  "http://localhost:5173",
  "https://app.example.com",
]);

export const server = new HttpServer()
  .cors({
    allowOrigin: (req) => {
      const origin = req.headers.get("origin");
      return origin && allowed.has(origin) ? origin : undefined;
    },
    allowCredentials: true,
    allowMethods: "GET, POST, PUT, PATCH, DELETE, OPTIONS",
    allowHeaders: "Content-Type, Authorization",
    maxAge: "3600",
  })
  .get("/health", (_req, res) => res.json({ ok: true }))
  .post("/echo", (req, res) => res.json({ body: req.body }));

if (import.meta.main) server.listen(5050);

Browser fetch

await fetch("http://localhost:5050/echo", {
  method: "POST",
  credentials: "include",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer …",
  },
  body: JSON.stringify({ hello: "world" }),
});

The browser may send an OPTIONS preflight first. ExpressAPI answers with 204 and the merged CORS headers - you do not register OPTIONS yourself.