Responses & Errors

Cookie Management

Mount the first-party cookie() middleware to get a Cookie Jar Instance in your handlers. Read, set, sign, and delete HTTP cookies.

Cookies are not available by default. Mount the first-party cookie() middleware from @taserjs/router/middleware/cookie on a root or scoped layout. Handlers under that layout receive a Cookie Jar Instance as { cookies }.


Root layout (src/routes/$.ts)

src/routes/$.ts
import { cookie } from "@taserjs/router/middleware/cookie";
import { t } from "@taserjs/router";

export default t.layout("/*").use(
  cookie({
    secret: process.env.COOKIE_SECRET || "your-secure-secret-key-32-chars-long",
    httpOnly: true,
    sameSite: "Lax",
    secure: process.env.NODE_ENV === "production",
    path: "/",
  }),
);

Scoped directory layout

Mount only where cookies are needed so sibling branches stay free of cookie headers:

src/routes/auth.ts
import { cookie } from "@taserjs/router/middleware/cookie";
import { t } from "@taserjs/router";

export default t.layout("/auth/*").use(
  cookie({
    secret: process.env.COOKIE_SECRET!,
    path: "/auth",
  }),
);

Mount before you use

After you mount cookie() on a layout, handlers under that layout can destructure {cookies}. Without the middleware, cookies are not available on the handler.


Reading Cookies

src/routes/me.get.ts
import { json } from "@taserjs/router/reply";
import { t } from "@taserjs/router";

export default t.get("/me").handler(({ cookies }) => {
  const theme = cookies.get("theme") ?? "light";
  const allCookies = cookies.get(); // all parsed request cookies

  return json({ theme, allCookies });
});

Setting Cookies

src/routes/preferences.post.ts
import { ok } from "@taserjs/router/reply";
import { z } from "zod";
import { t } from "@taserjs/router";

export default t
  .post("/preferences")
  .body(
    z.object({
      theme: z.enum(["light", "dark", "system"]),
    }),
  )
  .handler(({ cookies, req }) => {
    cookies.set("theme", req.body.theme, {
      path: "/",
      maxAge: 60 * 60 * 24 * 365,
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "Lax",
    });

    return ok({ success: true });
  });

Set-Cookie headers are applied on the response automatically — you do not append headers manually.


Cryptographically Signed Cookies

Pass secret when constructing cookie({ secret }). Signed helpers then use that default:

src/routes/auth/login.post.ts
import { json } from "@taserjs/router/reply";
import { z } from "zod";
import { t } from "@taserjs/router";

export default t
  .post("/auth/login")
  .body(z.object({ userId: z.string() }))
  .handler(async ({ cookies, req }) => {
    await cookies.setSigned("session_user", req.body.userId);
    return json({ message: "Logged in successfully" });
  });
src/routes/auth/session.get.ts
import { json, unauthorized } from "@taserjs/router/reply";
import { t } from "@taserjs/router";

export default t.get("/auth/session").handler(async ({ cookies, ctx }) => {
  const userId = await cookies.getSigned("session_user");

  if (!userId) {
    return unauthorized({ message: "Invalid or expired session cookie" });
  }

  const user = await ctx.db.getUser(userId);
  return json(user);
});

Deleting Cookies

src/routes/auth/logout.post.ts
import { json } from "@taserjs/router/reply";
import { t } from "@taserjs/router";

export default t.post("/auth/logout").handler(({ cookies }) => {
  const previousSession = cookies.delete("session_user", { path: "/" });
  return json({ message: "Logged out", previousSession });
});

  • prefix: "secure": Prepends __Secure- and enforces secure: true.
  • prefix: "host": Prepends __Host-, enforces secure: true, path: "/", and prohibits domain.
cookies.set("session", sessionId, { prefix: "host" });
const sessionId = cookies.get("session", "host");

CookieJarOptions Reference

Defaults passed to cookie(options) merge into every set / setSigned / delete call:

Prop

Type


Prop

Type