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 |
| — | page | 200 text/html |
Serve collocated route page |
| — | 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 route from "primate/route";
export default route({
get(request) {
return "Hello from TypeScript!";
},
});import route from "primate/route";
export default route({
get(request) {
return "Hello from TypeScript!";
},
});package main
import "github.com/primate-run/go/route"
var _ = route.Get(func(_ route.Request) any {
return "Hello from Go!"
})from primate import Route
@Route.get
def get(request):
return "Hello from Python!"require 'primate/route'
Route.get do |request|
'Hello from Ruby!'
endUse the explicit text handler for more options.
import http from "@rcompat/http";
import response from "primate/response";
import route from "primate/route";
export default route({
post(request) {
return response.text("Hello TypeScript!", { status: http.Status.CREATED });
},
});import http from "@rcompat/http";
import response from "primate/response";
import route from "primate/route";
export default route({
post(request) {
return response.text("Hello JavaScript!", { status: http.Status.CREATED });
},
});package main
import (
"github.com/primate-run/go/response"
"github.com/primate-run/go/route"
)
var _ = route.Post(func(request route.Request) any {
return response.Text("Hello from Go!", map[string]any{"status": 201})
})from primate import Response, Route
@Route.post
def post(request):
return Response.text("Hello from Python!", status=201)require 'primate/route'
require 'primate/response'
Route.post do |request|
Response.text("Hello from Ruby!", status: 201)
endJSON
Return JSON-serializable objects to serve application/json.
import route from "primate/route";
export default route({
get() {
return [
{ name: "Donald" },
{ name: "John" },
];
},
});import route from "primate/route";
export default route({
get() {
return [
{ name: "Donald" },
{ name: "John" },
];
},
});package main
import (
"github.com/primate-run/go/core"
"github.com/primate-run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return []core.Dict{{"name": "Donald"}, {"name": "John"}}
})from primate import Route
@Route.get
def get(request):
return [{"name": "Donald"}, {"name": "John"}]require 'primate/route'
Route.get do |request|
[{ name: 'Donald' }, { name: 'John' }]
endUse the explicit json handler for more options.
import response from "primate/response";
import http from "@rcompat/http";
import route from "primate/route";
export default route({
get() {
return response.json([
{ name: "Donald" },
{ name: "John" },
], { status: http.Status.CREATED });
},
});import response from "primate/response";
import http from "@rcompat/http";
import route from "primate/route";
export default route({
get() {
return response.json([
{ name: "Donald" },
{ name: "John" },
], { status: http.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";
export default route({
get() {
return new Blob(["data"]);
},
});import route from "primate/route";
export default route({
get() {
return 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 response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.binary(new Blob(["data"]), {
// set filename manually
headers: { "Content-Disposition": "attachment; filename=data.bin" },
});
},
});import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.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 fs from "@rcompat/fs";
import route from "primate/route";
export default route({
get() {
return fs.ref("/tmp/data.bin");
},
});import fs from "@rcompat/fs";
import route from "primate/route";
export default route({
get() {
return fs.ref("/tmp/data.bin");
},
});Redirect
Return a URL to redirect to another address.
import route from "primate/route";
export default route({
get() {
return new URL("https://example.com/login");
},
});import route from "primate/route";
export default route({
get() {
return 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";
export default route({
get() {
return response.redirect("https://primate.run", Status.SEE_OTHER);
},
post(request) {
return response.redirect(`/login?next=${request.target}`);
},
});import Status from "@rcompat/http/Status";
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.redirect("https://primate.run", Status.SEE_OTHER);
},
post(request) {
return response.redirect(`/login?next=${request.target}`);
},
});package main
import (
"github.com/primate-run/go/response"
"github.com/primate-run/go/route"
)
var _ = route.Get(func(_ route.Request) any {
return response.Redirect("https://primate.run", 303)
})
var _ = route.Post(func(_ route.Request) any {
return response.Redirect("/login")
})from primate import Response, Route
@Route.get
def get(request):
return Response.redirect("https://primate.run", 303)
@Route.post
def post(request):
return Response.redirect("/login")require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.redirect('https://primate.run', 303)
end
Route.post do |request|
Response.redirect('/login')
endView
Render and serve components from the views directory as
text/html.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.view("Counter.jsx");
},
});import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.view("Counter.jsx");
},
});package main
import (
"github.com/primate-run/go/response"
"github.com/primate-run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.View("Counter.jsx")
})from primate import Response, Route
@Route.get
def get(request):
return Response.view("Counter.jsx")require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.view('Counter.jsx')
endProps
Populate the component with initial props.
import response from "primate/response";
import route from "primate/route";
export default route({
get(request) {
return response.view("Counter.jsx", { start: 10 });
},
});import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.view("Counter.jsx", { start: 10 });
},
});package main
import (
"github.com/primate-run/go/core"
"github.com/primate-run/go/response"
"github.com/primate-run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.View("Counter.jsx", core.Dict{"start": 10})
})from primate import Response, Route
@Route.get
def get(request):
return Response.view("Counter.jsx", {"start": 10})require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.view('Counter.jsx', start: 10)
endPage
Components are embedded into your app's main HTML template at templates/app.html,
with the component code replacing the %body% placeholder. If the app template
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 template option to use another HTML template.
import response from "primate/response";
import route from "primate/route";
// render into `templates/counter.html`
export default route({
get() {
return response.view("Counter.jsx", { start: 10 },
{ template: "counter.html" });
},
});
Placeholders
You can use placeholders in your HTML templates.
<!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";
export default route({
get() {
return response.view("Counter.jsx", { start: 10 }, {
placeholders: {
title: "Counter",
},
});
},
});
Partial
Pass a partial: true option to render the component without the enclosing
HTML template.
import response from "primate/response";
import route from "primate/route";
// will render Counter without embedding it into templates/app.html
export default route({
get() {
return response.view("Counter.jsx", { start: 10 }, { partial: true });
},
});This is useful for replacing parts of the page whilst retaining the HTML template.
Page
Render a frontend component collocated with the current route using
response.page. This keeps the route handler and its page component next to each
other.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.page({ message: "Hello" });
},
});The matching component uses the same basename as the route and a frontend extension.
import type route from "./index";
export default function Page(props: typeof route.get.Page) {
return <h1>{props.message}</h1>;
}response.page accepts the same props and view options as response.view.
Layouts can use response.page too. A +layout.ts route looks for a collocated
+layout component, such as +layout.tsx, +layout.svelte, or
+layout.vue.
Error
Serve a 404 Not Found error page as text/html.
import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.error({ body: "Not Found" });
},
});import response from "primate/response";
import route from "primate/route";
export default route({
get() {
return response.error({ body: "Not Found" });
},
});package main
import (
"github.com/primate-run/go/response"
"github.com/primate-run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.Error(map[string]any{
"body": "Not Found",
})
})from primate import Response, Route
@Route.get
def get(request):
return Response.error({"body": "Not Found"})require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.error(body: 'Not Found')
endThis handler uses the HTML file at templates/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";
export default route({
get(request) {
return response.error({
status: Status.INTERNAL_SERVER_ERROR,
});
},
});import Status from "@rcompat/http/Status";
import response from "primate/response";
import route from "primate/route";
export default route({
get(request) {
return response.error({
status: Status.INTERNAL_SERVER_ERROR,
});
},
});package main
import (
"github.com/primate-run/go/core"
"github.com/primate-run/go/response"
"github.com/primate-run/go/route"
)
var _ = route.Get(func(request route.Request) any {
return response.Error(core.Dict{"status": 500})
})from primate import Response, Route
@Route.get
def get(request):
return Response.error({"status": 500})require 'primate/route'
require 'primate/response'
Route.get do |request|
Response.error(status: 500)
endAs with view, you can pass a different template option to use another HTML template.
import response from "primate/response";
import route from "primate/route";
// use templates/custom-error.html instead of templates/error.html
export default route({
get() {
return response.error({ template: "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";
export default route({
get() {
return 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";
export default route({
get() {
return response.sse(source => {
// push event to client
source.send("open", "hi!");
});
},
});response.sse receives a setup function. Return a cleanup function to stop
timers, unsubscribe from event channels, or release any other resources when the
client disconnects.
return response.sse(source => {
const timer = setInterval(() => {
source.send("tick", Date.now());
}, 1000);
return () => clearInterval(timer);
});Call source.close() to end the stream from the server. This closes the
response cleanly and runs the returned cleanup function. Cleanup runs exactly
once whether the server closes the stream or the client disconnects.
For app-level pub/sub, use primate/events and return the
unsubscribe function from the SSE setup.
Response
Return a custom Response.
import http from "@rcompat/http";
import route from "primate/route";
export default route({
get() {
return new Response("Hi!", {
status: http.Status.ACCEPTED,
headers: { "X-Custom": "1" },
});
},
});
ResponseLike reference
type ResponseLike =
| string
| Record<string, unknown>
| Record<string, unknown>[]
| Blob
| ReadableStream
| URL
| null
| Response
;