norns

route()

Wrap a +server.c handler with body parsing, validation, container resolution and JSON (or TRON) serialization.

import { messages } from '$lib/norns/messages/server/public'
import { sendMessageSchema } from '$lib/norns/messages/shared/schema'

export GET := route
	handler: ({ container }) =>
		data: messages(container).list()

export POST := route
	input: sendMessageSchema
	handler: ({ input, container }) =>
		messages(container).create input
		{ ok: true }

route is auto-imported in server files.

#What the wrapper does, in order

  1. Body parsing, only when input is set. application/json is parsed as JSON; application/x-www-form-urlencoded and multipart/form-data are read with formData() and flattened. An app-wide or per-route serializer can claim other content types first through its parseBody hook (this is how TRON request bodies work). Empty or unsupported bodies become null, and the schema decides whether null is acceptable.
  2. Body validation against input. Failure throws error(400, { message, issues }).
  3. Query validation against query, over Object.fromEntries(url.searchParams). Same 400 on failure.
  4. Handler call with { input, query, container, event, user }.
  5. Serialization. A Response returned by the handler passes through untouched. Otherwise the serializer (per-route, else app-wide) may turn the value into a Response; if it declines, the value is sent as JSON with json(). undefined becomes null.

#Options

Option Notes
input body schema: a valibot schema, any Standard Schema, or a function
query query-string schema, same forms
serializer per-route serializer; null forces plain JSON even when an app-wide one is set
handler required

For non-success outcomes throw error(...) or redirect(...) inside the handler; SvelteKit surfaces them.

#Returning something that is not JSON

Return a Response yourself:

export GET := route
	handler: ({ container }) =>
		new Response(docs(container).sitemap(), { headers: { 'content-type': 'application/xml' } })

#Calling it from the browser

Plain fetch works. With norns-tron installed, api.get('/api/messages') sends Accept: application/tron and decodes either format transparently.