PilloFoundation / duvet

12 stars 0 forks source link

Middleware support #2

Closed PKWadsy closed 3 months ago

PKWadsy commented 5 months ago

Kint is very powerful already for writing APIs extremely quickly. However, it is still missing a core feature that would take it to the next level: middleware. As Kint is mounted onto an express app, you can technically use middleware by running expressApp.use(middleware) before running expressApp.use('/', kintRouter) where kintRouter is the router built by the buildExpressRouter function. This allows you to add middleware that is run before every handler. In fact, you have to do this with the express.json() middleware for kint to work anyway. It is also possible to provide middleware to subroutes by manually building routes at subdirectories and running subRouter.use(middleware). But it would be nice if this could be done automatically by the framework.

Some useful middlewares may include:

One limitation of express middlewares is that they can modify request and response objects in ways that are not predictable or type-safe. An alternative could be to have a function that extends the defineExpressEndpoint function, possibly like so:

// routes/user/kint.ts
import { makeEndpoint as base } from '../../../kint.ts';
import kint from 'express-kint';

export const makeEndpoint = kint.extend(base, middleware);

This way, middleware would be able to extend the context, body, request or response objects to provide additional data.

One limitation of this is that you would have to change many of your imports if you wanted to add middleware to a subRoute. Although this can be done quite easily with a find and replace in your editor.

Regardless, it may be overkill to only provide such a method for adding middleware. Some middlewares don't modify any objects. In this case, normal express middleware should also be supported.

Regardless of the type of middleware you use, it should be easy to apply middleware to different scopes as well. A minimum set of supported scopes would be

Next, we can add a bit more flexibility with matchers e.g.

The middleware should take the definition block as an argument which allows developers to configure how the middleware behaves on a per-endpoint basis.

One way to implement this middleware system would be to have a kint.ts file at any level in the routes folder which can export both middleware functions and configuration objects which handlers can inherit. You can define a policy about how to apply the middleware. You can also provide an endpoint definition object which can be inherited by child endpoints.

PKWadsy commented 5 months ago

Perhaps worth extracting all the zod stuff into a new middleware

joshuabrownenz commented 5 months ago

I think moving Zod to be a piece of middleware makes a lot of sense. It would be easy to put into the default project and/or recommended usage.

One possibility would be having mutating and non-mutating middleware. Where you explicitly have to say that requests (and responses 🤷‍♂️) could be modified by the middleware. Middleware like Zod could complain if it is not the closest to user operation for requests therefore guaranteeing the format of requests is valid.

joshuabrownenz commented 5 months ago

I think for a proof of concept to validate this approach we could develop

PKWadsy commented 5 months ago

I think moving Zod to be a piece of middleware makes a lot of sense. It would be easy to put into the default project and/or recommended usage.

One possibility would be having mutating and non-mutating middleware. Where you explicitly have to say that requests (and responses 🤷‍♂️) could be modified by the middleware. Middleware like Zod could complain if it is not the closest to user operation for requests therefore guaranteeing the format of requests is valid.

I agree. We could even make an "opinionated" middleware which is just a collection of existing middlewares which essentially wraps a whole bunch of other middlewares

PKWadsy commented 3 months ago

Middleware was added with #18