Chapter 6 of 7

Add an API route

Serve a random book as JSON, then call it from an island with a client that knows its types.

On this page

Write the route

Files in app/api answer under /api. Export a function named for each HTTP method you handle:

import { json } from "optz";
import { books } from "$lib/books";

export const GET = () => json(books[Math.floor(Math.random() * books.length)]);

Try it:

curl http://127.0.0.1:3001/api/books/random
{"slug":"the-pragmatic-programmer","title":"The Pragmatic Programmer","author":"Andrew Hunt and David Thomas","summary":"Habits that keep software easy to change."}

Call it with types

api() from optz knows every route in your app and what each one returns:

import { useState } from "react";
import { api } from "optz";

export default function RandomBook() {
  const [title, setTitle] = useState<string>();
  const pick = async () => {
    const book = await api("/api/books/random");
    setTitle(book.title);
  };
  return (
    <div className="mt-8">
      <button type="button" onClick={pick} className="rounded border px-3 py-1">Pick one for me</button>
      {title && <p className="mt-2">Try {title}.</p>}
    </div>
  );
}

Add <RandomBook /> under the list in app/routes/books/index.tsx (import it from $components/RandomBook.island) and press the button.

book.title is typed as a string because the route returns a Book. Ask for a route that doesn't exist, like api("/api/bookz"), and bun run check fails, the same way a bad link did.

Where to go from here

you want touse
Read a JSON body, typed for the caller toohandler() with HandlerContext<Params, Body>
Cache a response for everyoneexport const cache = { maxAge: 30, staleWhileRevalidate: 300 }
Answer from memory, computed at build timeexport const prerender = true
Route parametersA file like app/api/books/[slug].ts, read with params.slug

The framework reference covers each of these.

Next, build the app and put it on a server.