Primate Logo Primate

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.

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 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";

 // render into `pages/counter.html`

export default route({
  get() {
    return response.view("Counter.jsx", { start: 10 },
      { page: "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";

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

import response from "primate/response";
import route from "primate/route";

// will render Counter without embedding it into pages/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 page.

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

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

export default route({
  get() {
    return 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";

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({
      // connection opened
      open(source) {
        // push event to client
        source.send("open", "hi!");
      },
      // connection closed
      close() { },
    });
  },
});

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