Route

Features

Webroutes have many features and benefits, compared to using a web framework or writing raw request handlers. It sits somewhere between the two.

Declarative

// All route information is defined in one place, declaratively
export default route("/pathname")
	.params(MySchema)
	.handle((req) => ({ foo: "bar" }))

Define route information declaratively, not imperatively. This encourages setting API boundaries and makes composition and introspection easier.

Composable

// Define route w/ middleware
const  = ()
	.(() => ({ : { : 1 } }))
 
// Extend that route even more
const  = .("/me")
 
// Create a request handler off it
const  = 
	.("/update")
	.((, {  }) => {
		return .
user: {
id: number;
}
user
})

Compose previously declared middleware, paths or schema to produce powerful endpoints, without having to repeat yourself.

Atomic

posts/route.ts
export default routeWithMiddleware.handle(() => {/**...*/})
// This does not require any root-level app to e.g. run middleware, it is self-sufficient

Each route is complete, standalone and router agnostic. Upstream operations like middleware are always traceable at the code level and baked in. Your route can now be used with any framework or runtime that uses web-standards.

Who uses web standards?

The WinterCG web standard is supported by Vercel, Cloudflare, Bun, Deno and many others.

Validation-first

route()
	// Declare schema with no performance overhead
	.params(ParamSchema)
	.query(QuerySchema)
	.headers(HeadersSchema)
	.body(BodySchema)
	.output(ResponseSchema)
	.handle(async (req, c) => {
		try {
			// Validation is only run here.
			// This avoids unnecessary work and enables custom error handling
			const { query, params, headers, body } = c.parse()
		} catch(e) {
			// TODO: Handle this error in a special way
		}
 
		return { /**...*/ } // Return values will be parsed
	})
// ...

Webroutes encourage input and output validation and provide a first class interface. Use any validation library you want. Since you've defined these schema up-front, producing things like OpenAPI specs becomes a piece of cake.

Future-proof

// Nothing weird or magical happening here...
 
const request = new Request("https://myurl.com")
const response = await myRoute(request)
 
expect(await response.json()).toBe({ /*...*/ })

Built on web-standards, webroutes don't do anything funky, opaque or complex. It is complex enough to want to avoid rewriting for each app, but simple enough to not feel to magical (and brittle).

Immutable

route()
.use((req) => {
	const update = { user: { id: 1 } }
 
	// Type-safe mutations
	return update;
})
.handle((req) => {
	// Instead of adding arbitrary properties to the request object
	req.user = {}
})
 

webroutes prioritise immutabability, never mutating request or response objects, nor monkey-patching any APIs. The boundaries for extra functionality are explicit and never crossed.

Flexible

Webroutes work with your existing stack. And they'll still work without them.

Like validation libraries...

validator-agnostic.ts
route().params(ZodSchema).body(YupSchema)

Nextjs...

nextjs/route.ts
export const GET = myRoute

Bun...

bun-api.ts
Bun.serve({
	fetch: myRoute(req)
});

Deno...

deno-api.ts
Deno.serve(myRoute);

Node...

import { serve } from "@hono/node-serve"
 
serve({
	fetch: myRoute
})

And many more...

But what about routing?

Find out how to achieve routing in Routing

On this page