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:
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:
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:
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:
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):
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 Function | HTTP Status | Typical Usage |
|---|---|---|
ok(data?, init?) | 200 OK | Generic success response |
created(data?, init?) | 201 Created | Resource created successfully |
accepted(data?, init?) | 202 Accepted | Async job accepted for processing |
noContent(init?) | 204 No Content | Success with empty payload |
redirect(location, init?) | 302 Found | Temporary redirection |
badRequest(data?, init?) | 400 Bad Request | Client query or payload error |
unauthorized(data?, init?) | 401 Unauthorized | Missing or invalid authentication token |
forbidden(data?, init?) | 403 Forbidden | Authenticated user lacks permission |
notFound(data?, init?) | 404 Not Found | Resource or route does not exist |
conflict(data?, init?) | 409 Conflict | Duplicate key or concurrency conflict |
payloadTooLarge(data?, init?) | 413 Payload Too Large | Request body exceeds maximum size limit |
unsupportedMediaType(data?, init?) | 415 Unsupported Media Type | Payload MIME/Content-Type is unsupported |
unprocessableEntity(data?, init?) | 422 Unprocessable Entity | Schema validation error |
tooManyRequests(data?, init?) | 429 Too Many Requests | Rate limit or quota exceeded |
internalServerError(data?, init?) | 500 Internal Server Error | Unhandled exception |
notImplemented(data?, init?) | 501 Not Implemented | Endpoint/feature not supported |
badGateway(data?, init?) | 502 Bad Gateway | Upstream service failure |
serviceUnavailable(data?, init?) | 503 Service Unavailable | Server maintenance or overloaded |
gatewayTimeout(data?, init?) | 504 Gateway Timeout | Upstream gateway/proxy timed out |
Example Usage of Error Helpers
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:
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
Handling Validation Errors
Catch and format input validation failures using ValidationError. Return structured JSON error envelopes with issue paths and messages.
Cookie Management
Read, set, sign, and delete HTTP cookies using ctx.cookies. Configure global cookie defaults and cryptographically signed session cookies.