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>:
// 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 Response — Response.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:
// 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:
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 /
useActionflow — no URL, nofetchboilerplate.
They coexist in the same app.