Route

Basics

import { route } from "@webroute/route";

A webroute is ultimately a declaration of an API endpoint, including validation, headers, middleware and the request handler.

By defining route information in one place, it makes applications easier to reason about, test and decouples routes from routing or other orchestration. This also means webroutes are agnostic to framework – so long as a Request and Response instance is on-hand.


A simple route can be as minimal as

route().handle((req) => {
  return new Response();
});

But this provides no benefit over using a regular (req: Request) => Response handler. In fact the result is exactly this.

webroute provides several (optional) ways of enhancing our route substantially, without adding much complexity.

Paths

It's often helpful to define our route path in the same place as our route, in a declarative fashion. webroute supports string path patterns.

route("/path/:param").handle(() => {});

This approach is highly declarative and inverts control compared to traditional API frameworks, which grants us more freedom with how we might want to execute our app.

In theory we can use any path pattern style here. Exactly what style you should use will depend on how you aim to implement routing, if at all. Read more in Routing

Method

We can define the method(s) of a route in a similar fashion.

route("/posts").method("get");
// or
route("/posts").method(["get", "post" /*...etc*/]);

Validation

Any production API should be validating input and output to ensure the right information is coming in or going out. We can validate many different parts of a request.

Specifying the schema does not cause webroute to automatically run any validation or parsing. This improves performance and allows you to better control e.g. failed validation.

The below examples will use Zod schema, but you may use whatever validation you like. Most popular validation libraries are supported.

Path Parameters

// GET /posts/123
("/posts/:id")
  .("get")
  .(.({ : .() }))
  .(async (, {  }) => {
    // Run the parsing/validation, lazily
    const { 
const id: number
id
} = await ("params");
});

Query Parameters

// GET /posts?limit=1&offset=2
 
("/posts")
  .("get")
  .(
    .({
      : .().(),
      : .().(),
    })
  )
  .(async (, {  }) => {
    // Run the parsing/validation, lazily
    const { , 
const offset: number | undefined
offset
} = await ("query");
});

Incoming Headers

// GET /posts -H "Authorization: Bearer eYon...."
 
("/posts/:id")
  .("get")
  .(.({ : .().(/Bearer [a-zA-Z0-9]+/) }))
  .(async (, {  }) => {
    // Run the parsing/validation, lazily
    const { 
const Authorization: string
Authorization
} = await ("headers");
});

Request Body

// POST /login -D { "email": "...", "password": "..." }
 
("/login")
  .("post")
  .(
    .({
      : .().(),
      : .().(10, "Password too short"),
    })
  )
  .(async (, {  }) => {
    // Run the parsing/validation, lazily
    const { , 
const password: string
password
} = await ("body");
});

Output (response)

// GET /balance
 
("/balance")
  .("get")
  .(
    .({
      : .().(() => ()),
    })
  )
  .(() => {
    return { : 1_000_000 };
    // <- "1,000,000"
  });

Providers

Providers enable injecting services into the handler context via arbitrary initialisation functions.

 
()
  .({
    : () => new (),
    // Use arbitrary params
    : (: Request) => .("thing"), // Use a DI framework?
  })
  .((, ) => {
    ..().();
 
    ..();
  });

Middleware

Middleware enables composing common functionality, with complete type-safety.

 
()
  .(() => {
    return { : "123" }; // Adds to state
  })
  .((, ) => {
    ..;
  });

Learn more about Middleware.

Route Utilities

The route object also exposes some type utilities for type inference and extracting route data.

The following examples reference the below route definition

example-route.ts
// Define some route
const  = ("/post/:id")
  .(["put", "post"])
  .(.({ : .() }))
  .(.({ : .() }))
  .(.({ : .() }))
  .(.({ : .() }))
  .(() => ({ : true }));

Route Data

const  = Route.();
const  = Route.();
const  = Route.();
 

Type Inference

// Infer parts independently
type  = Route.<typeof >;
type  = Route.<typeof >;
 
// Infer schema shapes
// In = before transform
type  = Route.<typeof >;
type  = Route.<typeof >;
type  = Route.<typeof >;
type  = Route.<typeof >;
 
// Out = after transform (if any)
// type ParamsOut = route.InferParamsOut<typeof myRoute>
// ...
 
// Or infer all at once
type  = Route.<typeof >;

webroute also provides some client-side type inference, which greatly speeds up development on the client side. Visit the Client docs.

On this page