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');
Constraints
- You must either return something JSON serializable, a
Response
object, or throw an error - Route execution logic is async
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:
-
A
KaitoRequest
object - This is a thin wrapper around the standard WebRequest
object, providing a similar API -
A
KaitoResponse
object - This is very different to theResponse
object. It lets you- Access and mutate response headers
- Mutate the status code
- Get the headers and status code
And then the router handles requests very similarly to the following:
const kaitoRequest = new KaitoRequest(reqFromServer); // reqFromServer is a `Request` instance
const kaitoResponse = new KaitoResponse(); // Only for getting/setting headers & status
const context = await getContext(kaitoRequest, kaitoResponse);
const result = await route(context);
if (result instanceof Response) {
return result;
}
// Create the final response object using the
// headers and status code from the KaitoResponse object
const response = Response.json(result, {
status: kaitoResponse.statusCode, // status will always default to 200
headers: kaitoResponse.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
KaitoResponse
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 KaitoResponse
object. This gives you full control when needed, but means you need to set all headers and status codes on the Response
object itself.
Execution
Routes are executed by the router. The router is responsible for parsing the request, validating the input, and executing the route. Route run methods must be async. Throwing an error will call your onError handler defined in your server.
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.