Webroute

Validation

Typically, input/output validation is offered as an extension or plugin. With webroute, I/O validation is a first-class citizen and can be declared easily.

Overview

We can define several different types of input and output using the route utility, including query params, path params, request body and headers, as well as response data.

route()
  .query(SomeSchema) // Query params
  .params(SomeSchema) // Path params
  .body(SomeSchema) // Request body
  .headers(SomeSchema) // Request headers
  .output(SomeSchema); // Response, or "output", data

The output term is used instead of reponse to disambiguate from the HTTP Response.

Declared input schema are not automatically validated. Webroute requires explicit parsing of the inputs, which can be done via the .parse() context method.

route()
  // ---input schema defined here---
  .handle(async (req, c) => {
    // Parse everything at once
    const { query, params, body, headers } = await c.parse();
  });

While this adds slightly more work, it makes custom error handling significantly more flexible, including where and how parsing occurs.

By default, we will want to parse all inputs at once. However, at times we may want to parse parts of the input individually, which we can do like so.

// ...
const params = await c.parse("params");
// ...

The .parse() function is async only to accomodate asynchronous schema, and does not inherently carry out any asynchronous tasks.

Schema Library Support

Webroute is agnostic to schema validation library, meaning you can continue using most popular schema libraries.

The below examples will use zod.

Path Params

Path parameters are defined in the route path. We can validate these parameters by supplying a .params() schema.

const  = .({
  : .({ : true }),
});
 
("/user/:id")
  .()
  .(async (, ) => {
    const { 
const id: number
id
} = await .("params");
});

Inference

Path parameters are also automatically inferred as type string. This means we have some type-safety without providing any explicit schema.

("/user/:id")
  // No params defined, still some type-safety
  .(async (, ) => {
    const { 
const id: string
id
} = await .("params");
});

Query Params

Query params are typically used for secondary or optional data. They can be declared similar to path params.

const  = .({
  : .({ : true }).(),
});
 
()
  .()
  .(async (, ) => {
    const { 
const limit: number | undefined
limit
} = await .("query");
});

Request Headers

Some routes may require headers, which we can specify via .headers().

const  = .({
  : .().(/Bearer [a-zA-Z0-9]+/),
});
 
()
  .()
  .(async (, {  }) => {
    const { 
const Authorization: string
Authorization
} = await ("headers");
});

Request Body

We can specify request body schema using .body().

 
()
  .(.({ : .().() }))
  .(async (, {  }) => {
    const 
const body: {
    email: string;
}
body
= await ("body");
});

Output (response)

In addition to validating whether our output is the correct shape, we often want to add, remove or format the response data. The .output() method allows specifying schema to do exactly that.

 
const  = .({
  : .().(),
});
 
("/balance")
  .("get")
  .()
  .(() => {
    return { : 1_000_000 };
    // <- "1,000,000"
  });
Output validation is automatic

Unlike input schema, outputs are validated by webroute itself. If any errors are thrown in the process, a root-level handler should be used to catch any of these unexpected validation issues.

Skipping Output Parsing

Returning a Response from a handler will skip any validation. While returned data is treated as JSON, a Response is returned to the user as is (with the exception of any middleware effects).

On this page