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 */
...
