Primate LogoPrimate

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.

TypeScript JavaScript Go Python Rubyroutes/text.tsroutes/text.jsroutes/text.goroutes/text.pyroutes/text.rb
import route from "primate/route";

export default route({
  get(request) {
    return "Hello from TypeScript!";
  },
});

Use the explicit text handler for more options.

TypeScript JavaScript Go Python Rubyroutes/text.tsroutes/text.jsroutes/text.goroutes/text.pyroutes/text.rb
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 });
  },
});

JSON

Return JSON-serializable objects to serve application/json.

TypeScript JavaScript Go Python Rubyroutes/json.tsroutes/json.tsroutes/json.goroutes/json.pyroutes/json.rb
import route from "primate/route";

export default route({
  get() {
    return [
      { name: "Donald" },
      { name: "John" },
    ];
  },
});

Use the explicit json handler for more options.

TypeScript JavaScriptroutes/json.tsroutes/json.ts
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).

TypeScript JavaScriptroutes/binary.tsroutes/binary.ts
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.

TypeScript JavaScriptroutes/binary.tsroutes/binary.ts
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.

TypeScript JavaScriptroutes/binary.tsroutes/binary.ts
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.

TypeScript JavaScriptroutes/redirect.tsroutes/redirect.ts
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.

TypeScript JavaScript Go Python Rubyroutes/redirect.tsroutes/redirect.tsroutes/redirect.goroutes/redirect.pyroutes/redirect.rb
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}`);
  },
});

View

Render and serve components from the views directory as text/html.

TypeScript JavaScript Go Python Rubyroutes/index.tsroutes/index.jsroutes/index.goroutes/index.pyroutes/index.rb
import response from "primate/response";
import route from "primate/route";

export default route({
  get() {
    return response.view("Counter.jsx");
  },
});

Props

Populate the component with initial props.

TypeScript JavaScript Go Python Rubyroutes/index.tsroutes/index.jsroutes/index.goroutes/index.pyroutes/index.rb
import response from "primate/response";
import route from "primate/route";

export default route({
  get(request) {
    return response.view("Counter.jsx", { start: 10 });
  },
});

Page

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.

TypeScriptroutes/index.ts
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.

TypeScriptroutes/index.tsx
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.

TypeScript JavaScript Go Python Rubyroutes/not-found.tsroutes/not-found.jsroutes/not-found.goroutes/not-found.pyroutes/not-found.rb
import response from "primate/response";
import route from "primate/route";

export default route({
  get() {
    return response.error({ body: "Not Found" });
  },
});

This 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.

TypeScript JavaScript Go Python Rubyroutes/error.tsroutes/error.jsroutes/error.goroutes/error.pyroutes/error.rb
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,
    });
  },
});

As 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
  ;
Previous
Requests
Next
Views