typescript - Åsidosätt förfrågningstyp i next-connect middleware


Jag använder next-connect med next.js och TypeScript, och jag vill skapa en middleware som lägger till några fält i request-objektet och härleder den nya request-typen. Koden nedan:

// 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: se kod

Svar #1

Jag lyckades implementera som ett exempel på lösning. Här är koddemon: stakc-blitz modifierad

Exempelbeskrivning av tillvägagångssättet

Jag har inte testat det men jag ville visa tillvägagångssättet.

Vi behöver en kontrollroutbyggare för att göra det. Och denna kontrollbyggare kommer att behöva "stapla" typändringarna till Request-objektet från all mellanprogramvara.

Ett prov

class ControllerBuilder<RequestType> {
   addMiddleWare(middleWare): ControllerBuilder<RequestType & middlewareTypeAdditions> {
   // implementation
 }
}

För att kunna extrahera middleware-typen behöver jag att den anges någonstans. Det är därför jag introducerade ett dekorerat middleware.

Här är abstraktionen över Decorate-middleware:

abstract class DecoratedMiddleware<MiddlewareReqTypeAdditions> {
  ///
}

Nu i ControllerBuilder kan vi "extrahera typen" av varje middleware och "stapla" dem genom att returnera en ny instans med typen: RequestType hittills förenad med tillägget som det nya middleware kommer att lägga till

class ControllerBuilder<RequestType> {
   addMiddleWare(middleWare: DecoratedMiddleware<MiddlewareReqTypeAdditions>): ControllerBuilder<RequestType & MiddlewareReqTypeAdditions> {
   // implementation
   return new ControllerBuilder<>
 }
}

Här är en exempelimplementation av middleware. Vi behöver bara ange de ytterligare egenskaperna för förfrågan, som byggaren kommer att ställa in. Processfunktionen måste returnera ett Promise av dessa egenskaper, vilket säkerställer att allt är inställt enligt middleware-typens kontrakt.

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,
    });
  };
}

Och till sist ett exempel på användning:

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 });
  });

Byggaren är inte 100% färdig, min avsikt var att visa tillvägagångssättet. Jag skulle förmodligen ändra det så att ett set med mellanprogram kan användas.

Observera att den beter sig som Builder tills handle anropas. Så den är oföränderlig och kan kedjas samt återanvändas.

Något sådant här:

const authUserWithWeather = ControllerBuilder.create()
  .addMiddleware(authMiddleware)
  .addMiddleware(weatherMiddleware);


authUserWithWeather.get("/").handle(() => {});
authUserWithWeather
  .addMiddleware(multipartMiddleware)
  .get("/something")
  .handle(() => {})

Länk till demon igen: stakc-blitz modifierad

Svar #2

Jag gick för en hackig men enkel lösning i min app där jag bara har en enda ändring jag vill göra i typerna. Jag har i princip viss logik i en middleware-funktion som jag vet kommer garantera att en viss egenskap finns på mitt request-objekt om det klarar sig förbi middleware, så jag skapar routern med request-typen med den ytterligare egenskapen inuti util. Det är definitivt inte generiskt, men för enklare fall fungerar det.

import { NextApiRequest, NextApiResponse } from 'next'
import { createRouter } from 'next-connect'
import { getAuth } from 'my-auth'

type NextApiRequestAuthed = NextApiRequest & {
  userId: string
}

export function getAuthRouter<ResponseData = any>() {
  const router = createRouter<NextApiRequestAuthed, NextApiResponse<ResponseData>>()
  router.use(async (req, res, next) => {
    const auth = getAuth(req)
    if (auth.userId == null) {
      return res.status(400)
    }
    req.userId = auth.userId
    await next()
  })
  return router
}

Då kan det helt enkelt användas så här:

type ResponseData = { data: string }
const router = getAuthRouter<ResponseData>()

router.get(async (req, res) => {
  const { userId } = req /* type of req is NextApiRequestAuthed */
  ...

Relaterade artiklar