Routes

Routes are the basic building blocks of Kaito. They represent a single HTTP route with a optional input schemas (body, query) and execution logic.

Creating a Route

Creating a route requires us to have a router already established. If you don’t, please checkout the getting started guide.

Here’s an extremely basic example of a ping/pong route.

const app = router().get('/ping', async () => 'pong');
💡
You must either return something JSON serializable, a Response object, or throw an error

Request/Response model

Understanding how Kaito handles requests and responses is crucial, so let’s cover that first.

For each incoming request, Kaito creates two important objects:

  1. A KaitoRequest object - This is a thin wrapper around the standard Request object, providing a similar API

  2. A KaitoHead object - This is a wrapper around a Headers object and a status code. It’s used so the router knows what changes you might have made to the status code or headers.

And then the router handles requests very similarly to the following:

const kaitoRequest = new KaitoRequest(reqFromServer); // reqFromServer is a `Request` instance
const kaitoHead = new KaitoHead();
 
const context = await getContext(kaitoRequest, kaitoHead);
const result = await route(context);
 
if (result instanceof Response) {
	return result;
}
 
// Create the final response object using the
// headers and status code from the kaitoHead object
const response = Response.json(result, {
	status: kaitoHead.status(), // status will always default to 200
	headers: kaitoHead.headers,
});
 
return response;

So to summarise

  • Check if you returned a Response instance, and if so, return that
  • Otherwise get the headers and status code from the KaitoHead object
  • Automatically set Content-Type: application/json if you return a JSON-serializable value
  • Use these to build the final response

Ultimately the important thing to understand here is that if you return a Response instance directly, Kaito will use that as-is and ignore any changes made to the KaitoHead object. This gives you full control when needed, but means you need to set all headers and status codes on the Response object itself.

Input

Routes can also take a query and body schema provided by Zod. Internally, Kaito wil validate all request bodies and query params so you can be absolutely certain you are processing the right data.

Route query schemas should always take a string, or array of strings as the input. This is because query params are always strings. It is safe to transform them into other types, but you should always be able to handle a string.

import {z} from 'zod';
 
const router = router().post('/echo', {
	query: {
		skip: z.string().transform(value => parseInt(value)),
		take: z.string().transform(value => parseInt(value)),
	},
	body: z.number(),
	async run({body, query}) {
		// Echo it back
		return {body, query};
	},
});

Zod schemas can be of any shape or size, including objects, booleans, numbers and literals. For more reference, please read the Zod Documentation.