Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override request type in next-connect middleware

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

like image 705
Ismail_Aj Avatar asked Sep 04 '25 17:09

Ismail_Aj


1 Answers

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

like image 199
Svetoslav Petkov Avatar answered Sep 07 '25 17:09

Svetoslav Petkov