Murasaki

API Routes

Next.js-style file-based HTTP endpoints under src/api.

A src/api/<path>/route.ts file exports one function per HTTP method, served at /api/<path>:

src/api/hello/route.ts
// GET /api/hello
import type { RouteHandler } from 'murasaki'

export const GET: RouteHandler = async (request) => {
  return Response.json({ message: `Hello from Node ${process.version}` })
}

export const POST: RouteHandler = async (request) => {
  const body = await request.json()
  return Response.json({ received: body })
}

RouteHandler takes a Web Request and a context with params, and returns a Web ResponseResponse.json(...), new Response(...), status codes, headers, all standard.

Export any of GET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS — whichever ones a route needs. A request for a method the module doesn't export gets a 405; a request under /api/ that doesn't match any route gets a 404 rather than falling through to the app's HTML.

Dynamic segments

A [name] folder captures a segment, exposed on context.params:

src/api/greet/[name]/route.ts
// GET /api/greet/:name
import type { RouteHandler } from 'murasaki'

export const GET: RouteHandler = async (_request, { params }) => {
  return Response.json({ greeting: `Hello, ${params.name}!` })
}

Catch-all ([...path]) and optional catch-all ([[...path]]) folders are also supported. Their values are arrays of decoded path segments; an omitted optional catch-all is undefined:

src/api/files/[[...path]]/route.ts
import type { RouteHandler } from 'murasaki'

export const GET: RouteHandler = async (_request, { params }) => {
  return Response.json({ path: params.path ?? [] })
}

Calling a route

const res = await fetch('/api/hello')
const data = await res.json()

Handlers run on the server in both dev (a Vite middleware) and prod (the bundled Node server), so they can reach the filesystem, a database, or secrets.

API routes vs. server actions

Both run on the server — pick by shape:

  • API routes are app-local HTTP endpoints for the renderer and Node main process. Packaged apps protect them with an app-runtime session, so they are not public webhook endpoints.
  • Server actions (guide) are typed RPC wired into React 19's form / useAction flow — no URL, no fetch boilerplate.

They coexist in the same app.

Next

Improve this page on GitHub

On this page