Responses
Route handlers return a ResponseLike
value that Primate converts into a
WHATWG Response
. You can return simple values ("implicit" responses) or use
explicit handlers from primate/response
when you need to control all
aspects of the response.
Return value | Handler | Response | Notes |
---|---|---|---|
string |
text | 200 text/plain |
Serve plain text |
object |
json | 200 application/json |
Serve JSON |
Blob ·File ·FileRef |
binary | 200 application/octet-stream |
Stream contents |
URL |
redirect | 302 |
Redirect to URL |
— | view | 200 text/html |
Serve frontend component |
— | error | 404 text/html |
Show error page |
— | ws | 101 |
WebSocket upgrade |
— | sse | 200 text/event-stream |
Server‑sent events |
null |
— | 204 |
new Response(null) |
Response | — | as given | WHATWG Response |
Text
Return strings to serve text/plain
.
import pema from "pema";
import string from "pema/string";
import route from "primate/route";
route.get(request => {
const { name } = request.body.fields(pema({ name: string }));
return `Name '${name}' submitted`;
});
Use the explicit text
handler for more options.
import pema from "pema";
import string from "pema/string";
import response from "primate/response";
import Status from "primate/response/Status";
import route from "primate/route";
route.post(request => {
const { name } = request.body.fields(pema({ name: string.optional() }));
if (name === undefined) {
return response.text("No name specified", {
status: Status.UNPROCESSABLE_ENTITY,
});
}
return response.text(`Name '${name}' submitted`, { status: Status.CREATED });
});
JSON
Return JSON-serializable objects to serve application/json
.
import route from "primate/route";
route.get(() => [
{ name: "Donald" },
{ name: "John" },
]);
Use the explicit json
handler for more options.
import response from "primate/response";
import Status from "primate/response/Status";
import route from "primate/route";
route.get(() => response.json([
{ name: "Donald" },
{ name: "John" },
], { status: Status.CREATED }));
Binary
Return Blob
, File
, ReadableStream
or any object exposing { stream(): ReadableStream }
to serve application/octet-stream
(binary data).
import route from "primate/route";
route.get(() => new Blob(["data"]));
Primate attempts to read the source's name and MIME type if available. Use the
explicit binary
handler for more options.
import binary from "primate/response/binary";
import route from "primate/route";
route.get(() => binary(new Blob(["data"]), {
// set filename manually
headers: { "Content-Disposition": "attachment; filename=data.bin" },
}));
Use rcompat's FileRef
to conveniently load a file from disk and stream it out.
import FileRef from "@rcompat/fs/FileRef";
import route from "primate/route";
route.get(() => new FileRef("/tmp/data.bin"));
Redirect
Return a URL
to redirect to another address.
import route from "primate/route";
route.get(() => new URL("https://example.com/login"));
Use the explicit redirect
handler to vary the status or for local redirects.
import Status from "@rcompat/http/Status";
import response from "primate/response";
import route from "primate/route";
// another status
route.get(() => response.redirect("https://primate.run", Status.SEE_OTHER));
// local redirect
route.post(request => response.redirect(`/login?next=${request.target}`));
View
Render and serve components from the components
directory
as text/html
.
import response from "primate/response";
import route from "primate/route";
// render components/Counter.jsx, embed in pages/app.html and serve
route.get(() => response.view("Counter.jsx"));
Props
Populate the component with initial props.
import response from "primate/response";
import route from "primate/route";
route.get(() => response.view("Counter.jsx", { start: 10 }));
Page
Components are embedded into your app's main HTML page at pages/app.html
,
with the component code replacing the %body%
placeholder. If the app page
doesn't exist, Primate falls back to its standard one.
<!doctype html>
<html>
<head>
<title>Primate app</title>
<meta charset="utf-8" />
%head%
</head>
<body>%body%</body>
</html>
Pass a different page
option to use another HTML page.
import response from "primate/response";
import route from "primate/route";
route.get(() => response.view("Counter.jsx", { start: 10 },
{ page: "counter.html" })); // render into `pages/counter.html`
Placeholders
You can use placeholders in your HTML pages.
<!doctype html>
<html>
<head>
<title>%title%</title>
<meta charset="utf-8" />
%head%
</head>
<body>%body%</body>
</html>
Populate them in your routes.
import response from "primate/response";
import route from "primate/route";
route.get(() => response.view("Counter.jsx", { start: 10 }, {
placeholders: {
title: "Counter",
},
}));
Partial
Pass a partial: true
option to render the component without the enclosing
HTML page.
import response from "primate/response";
import route from "primate/route";
// will render Counter without embedding it into pages/app.html
route.get(() => response.view("Counter.jsx", { start: 10 }, { partial: true }));
This is useful for replacing parts of the page whilst retaining the HTML page.
Error
Serve a 404 Not Found
error page as text/html
.
import response from "primate/response";
import route from "primate/route";
route.get(() => response.error({
body: "Not Found", // default
}));
This handler uses the HTML file at pages/error.html
or falls back to a
standard one provided by Primate.
<!doctype html>
<html>
<head>
<title>Error page</title>
<meta charset="utf-8" />
%head%
</head>
<body>
<h1>Error page</h1>
<p>
%body%
</p>
</body>
</html>
You can pass a custom status to this handler.
import Status from "@rcompat/http/Status";
import response from "primate/response";
import route from "primate/route";
route.get(() => response.error({
status: Status.INTERNAL_SERVER_ERROR,
}));
As with view
, you can pass a different page
option to use another HTML page.
import response from "primate/response";
import route from "primate/route";
// use pages/custom-error.html instead of pages/error.html
route.get(() => response.error({ page: "custom-error.html" }));
WebSocket
Upgrade a GET
request to ws:
and handle open
, message
, and close
events.
import response from "primate/response";
import route from "primate/route";
route.get(() => response.ws({
open(socket) {
socket.send("hello");
},
message(socket, message) {
// echo
socket.send(String(message));
},
close(socket) {
console.log("socket closed");
},
}));
Server‑sent events
Push out events to the client as text/event-stream
.
import response from "primate/response";
import route from "primate/route";
route.get(() => response.sse({
// connection opened
open(source) {
// push event to client
source.send("open", "hi!");
},
// connection closed
close() { },
}));
Response
Return a custom Response
.
import Status from "primate/response/Status";
import route from "primate/route";
route.get(() => new Response("Hi!", {
status: Status.ACCEPTED,
headers: { "X-Custom": "1" },
}));
ResponseLike
reference
type ResponseLike =
| string
| Record<string, unknown>
| Record<string, unknown>[]
| Blob
| ReadableStream
| URL
| null
| Response
;