Type-safe routes shared by your server and client, powered by zod/mini (input validation + transforms), @remix-run/route-pattern (URL matching), and alien-middleware (typed middleware chaining). The router output is intended to be used with @hattip/core adapters.
pnpm add rouzer zodEverything is imported directly from rouzer.
// routes.ts
import * as z from 'zod/mini'
import { $type, route } from 'rouzer'
export const helloRoute = route('hello/:name', {
GET: {
query: z.object({
excited: z.optional(z.boolean()),
}),
// The response is only type-checked at compile time.
response: $type<{ message: string }>(),
},
})
export const routes = { helloRoute }The following request parts can be validated with Zod:
pathquerybodyheaders
Zod validation happens on both the server and client.
import { chain, createRouter } from 'rouzer'
import { routes } from './routes'
const middlewares = chain().use(ctx => {
// An example middleware. For more info, see https://github.com/alien-rpc/alien-middleware#readme
return {
db: postgres(ctx.env('POSTGRES_URL')),
}
})
export const handler = createRouter({
routes,
middlewares,
debug: process.env.NODE_ENV === 'development',
})({
helloRoute: {
GET(ctx) {
const message = `Hello, ${ctx.path.name}${ctx.query.excited ? '!' : '.'}`
return { message }
},
},
})import { createClient } from 'rouzer'
import { helloRoute } from './routes'
const client = createClient({ baseURL: '/api/' })
const { message } = await client.json(
helloRoute.GET({ path: { name: 'world' }, query: { excited: true } })
)
// If you want the Response object, use `client.request` instead.
const response = await client.request(
helloRoute.GET({ path: { name: 'world' } })
)
const { message } = await response.json()- Declare it in
routes.tswithroute(...)andzod/minischemas. - Implement the handler in your router assembly with
createRouter(…)({ ... }). - Call it from the client with the generated helper via
client.jsonorclient.request.