Responses & Errors

Reply Helpers

Send clean, status-discriminated HTTP responses using tree-shakeable reply helpers: json(), ok(), notFound(), and redirect().

Taser.js provides standalone reply helper functions and a unified reply object imported from @taserjs/router/reply to construct standardized, type-safe HTTP responses. Every helper returns a standard Web Response with its HTTP status code embedded into the TypeScript return type (ReplyOf<Status, Body>). Because each helper is an independent standalone function, only the helpers you use are bundled into your application.


JSON Responses (json)

The json() function serializes data into a JSON string and automatically sets the Content-Type: application/json header:

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

export default t.get("/users").handler((ctx) => {
  return json([
    { id: "1", name: "Alice" },
    { id: "2", name: "Bob" },
  ]);
});

You can pass an optional second argument to customize the HTTP status code or set additional response headers:

return json(
  { message: "Created successfully", id: "user_123" },
  {
    status: 201,
    headers: {
      "X-Custom-Header": "ProductService",
    },
  },
);

Plain Text Responses (text)

Use text() to return raw strings with Content-Type: text/plain; charset=utf-8:

src/routes/robots.txt.get.ts
import { text } from "@taserjs/router/reply";
import { t } from "@taserjs/router";

export default t.get("/robots.txt").handler(() => {
  return text("User-agent: *\nDisallow: /admin/\n");
});

HTML Responses (html)

Use html() to return HTML documents or markup strings with Content-Type: text/html; charset=utf-8:

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

export default t.get("/").handler(() => {
  return html("<h1>Welcome to Taser.js!</h1>");
});

Empty / No Content Responses (noContent)

For successful operations that do not return a payload (such as DELETE endpoints), use noContent(). It sets HTTP status 204:

src/routes/items/$id.delete.ts
import { noContent } from "@taserjs/router/reply";
import { t } from "@taserjs/router";

export default t.delete("/items/:id").handler(async (ctx) => {
  await ctx.db.deleteItem(ctx.params.id);
  return noContent();
});

Redirects (redirect)

Use redirect() to send HTTP redirects (default: 302 Found):

src/routes/old-docs.get.ts
import { redirect } from "@taserjs/router/reply";
import { t } from "@taserjs/router";

export default t.get("/old-docs").handler(() => {
  // Permanent 301 redirect:
  return redirect("/docs/getting-started", { status: 301 });
});

External Redirect Protection

By default, redirect() prevents unintended external open redirects. If you need to redirect to external URLs, pass { allowExternal: true }:

return redirect("https://github.com/taserjs/taserjs", { allowExternal: true });

Status Code Helpers Reference

All reply helpers are exported from @taserjs/router/reply:

Helper FunctionHTTP StatusTypical Usage
ok(data?, init?)200 OKGeneric success response
created(data?, init?)201 CreatedResource created successfully
accepted(data?, init?)202 AcceptedAsync job accepted for processing
noContent(init?)204 No ContentSuccess with empty payload
redirect(location, init?)302 FoundTemporary redirection
badRequest(data?, init?)400 Bad RequestClient query or payload error
unauthorized(data?, init?)401 UnauthorizedMissing or invalid authentication token
forbidden(data?, init?)403 ForbiddenAuthenticated user lacks permission
notFound(data?, init?)404 Not FoundResource or route does not exist
conflict(data?, init?)409 ConflictDuplicate key or concurrency conflict
payloadTooLarge(data?, init?)413 Payload Too LargeRequest body exceeds maximum size limit
unsupportedMediaType(data?, init?)415 Unsupported Media TypePayload MIME/Content-Type is unsupported
unprocessableEntity(data?, init?)422 Unprocessable EntitySchema validation error
tooManyRequests(data?, init?)429 Too Many RequestsRate limit or quota exceeded
internalServerError(data?, init?)500 Internal Server ErrorUnhandled exception
notImplemented(data?, init?)501 Not ImplementedEndpoint/feature not supported
badGateway(data?, init?)502 Bad GatewayUpstream service failure
serviceUnavailable(data?, init?)503 Service UnavailableServer maintenance or overloaded
gatewayTimeout(data?, init?)504 Gateway TimeoutUpstream gateway/proxy timed out

Example Usage of Error Helpers

src/routes/documents/$id.get.ts
import { forbidden, json, notFound, unauthorized } from "@taserjs/router/reply";
import { t } from "@taserjs/router";

export default t.get("/documents/:id").handler(async (ctx) => {
  const user = ctx.state.user;
  if (!user) {
    return unauthorized({ message: "Login required" });
  }

  const doc = await ctx.db.findDocument(ctx.params.id);
  if (!doc) {
    return notFound({ message: "Document not found" });
  }

  if (doc.ownerId !== user.id) {
    return forbidden({ message: "You do not own this document" });
  }

  return json(doc);
});

Setting Cookies

You can set, read, or delete cookies directly through ctx.cookies or by passing the Set-Cookie header in ReplyInit:

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

export default t.post("/auth/login").handler(async (ctx) => {
  const sessionToken = await createSession();

  return json(
    { success: true },
    {
      headers: {
        "Set-Cookie": `session=${sessionToken}; HttpOnly; Secure; SameSite=Strict; Path=/`,
      },
    },
  );
});

Next Steps