I'm using next-connect with next.js & typescript, and I want to create a middleware that adds some fields to request object and infer the new request type. The code below:
// multipart middleware
export type NextApiRequestMultipart = NextApiRequest & {
files: Files;
fields: Fields;
};
export function multipart(
config?: Options
) {
return async (
req: NextApiRequest,
res: NextApiResponse,
next: NextHandler
) => {
const { files, fields } = await parseForm(req, config);
(req as NextApiRequestMultipart).files = files;
(req as NextApiRequestMultipart).fields = fields;
return next();
};
}
export router().post(
multipart({ multiples: false }),
async (req, res) => {
// I want to access properties without statically typing the request
const { files, fields } = req;
}
);
StackBlitz repo: see code
I managed to implement as sample solution. Here is the code demo: stakc-blitz modified
Sample description of the approach
I have not tested it but I wanted to show the approach.
We need a controller router builder to do that. And this controller builder will need to "stack" the types additions to the Request object of all middleware.
A sample
class ControllerBuilder<RequestType> {
addMiddleWare(middleWare): ControllerBuilder<RequestType & middlewareTypeAdditions> {
// implementation
}
}
In order to extract the middleware type - I need to have it stated someware. That is why I introduced a decorated middleware.
Here is the abstraction over the Decorate middleware:
abstract class DecoratedMiddleware<MiddlewareReqTypeAdditions> {
///
}
Now in the ControllerBuilder we can "extract the type" of each middleware and "stack" them by returning new instance with unin type: ReqeustType so far united with the addition the new middleware will add
class ControllerBuilder<RequestType> {
addMiddleWare(middleWare: DecoratedMiddleware<MiddlewareReqTypeAdditions>): ControllerBuilder<RequestType & MiddlewareReqTypeAdditions> {
// implementation
return new ControllerBuilder<>
}
}
Here is a sample middleware implementation of. We only need to state the additional properties of the request, which the builder will set. The process function has to return a Promise of those props, ensuring that all is set according to the middleware type contract.
type AuthRequestAddtion = {
role: string;
id: number | string;
hotelId: number;
};
class AuthMiddleware extends DecoratedMiddleware<AuthRequestAddtion> {
protected process: MuddlewareFunc<AuthRequestAddtion> = (req, res) => {
return Promise.resolve({
id: 1,
role: 'GUEST',
hotelId: 3,
});
};
}
And finally a sample usage:
ControllerBuilder.get(router(), '/with-weather')
.addMiddleware(authMiddleware)
.addMiddleware(multipartMiddleware)
.addMiddleware(weatherMiddleware)
.handle(async (req, res) => {
//now we have types for all the middlewares
const hotelId = req.hotelId;
const files = req.files;
const temp = req.weather.temperature;
res.status(200).json({ hotelId, files, temp });
});
The builder is not 100% complete, my intention was to show the approach. I would probably modify it so that a set of middlwares can be used.
Note that it behaves as Builder until handle is called. So it is immutable and can be chained and result reused
Something like this:
const authUserWithWeather = ControllerBuilder.create()
.addMiddleware(authMiddleware)
.addMiddleware(weatherMiddleware);
authUserWithWeather.get("/").handle(() => {});
authUserWithWeather
.addMiddleware(multipartMiddleware)
.get("/something")
.handle(() => {})
Link to the demo again: stakc-blitz modified
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With