typescript - Fastify multipart/form-data-fel: "body måste vara ett objekt"
Jag använder fastify-multer och JSON Schema för att skicka multipartsformulärdata som kan inkludera en fil. Oavsett vad jag gör fortsätter Fastify att ge mig ett felaktigt svar:
{
"statusCode": 400,
"error": "Bad Request",
"message": "body must be object"
}
Här är min index.ts:
const server = fastify();
server.register(require("@fastify/cors"));
server.register(multer.contentParser).after(() => {
if (!isProdEnv) {
server.register(require("@fastify/swagger"), {
/* ... */
});
}
server.register(require("@fastify/auth")).after(() => {
server.decorate("authenticateRequest", authenticateRequest);
server.decorate("requireAuthentication", requireAuthentication);
server.addHook("preHandler", server.auth([server.authenticateRequest]));
server.register(indexRouter);
server.register(authRouter, { prefix: "/auth" });
server.register(usersRouter, { prefix: "/users" });
server.register(listsRouter, { prefix: "/lists" });
server.register(postsRouter, { prefix: "/posts" });
server.register(searchRouter, { prefix: "/search" });
server.register(settingsRouter, { prefix: "/settings" });
});
});
server.setErrorHandler((err, req, res) => {
req.log.error(err.toString());
res.status(500).send(err);
});
Och /posts/create-ändpunkten:
const postsRouter = (server: FastifyInstance, options: FastifyPluginOptions, next: HookHandlerDoneFunction) => {
server.post(
"/create",
{
schema: {
consumes: ["multipart/form-data"],
body: {
content: {
type: "string"
},
media: {
type: "string",
format: "binary"
},
"media-description": {
type: "string"
}
}
},
preHandler: [server.auth([server.requireAuthentication]), uploadMediaFileToCloud]
},
postsController.createPost
);
next();
};
export default postsRouter;
Begäran CURL:
curl -X 'POST' \
'http://localhost:3072/posts/create' \
-H 'accept: */*' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJoYW5kbGUiOiJ1bGtrYSIsInVzZXJJZCI6IjYyNGQ5NmY4NzFhOTI2OGY2YzNjZWExZCIsImlhdCI6MTY1NzEwNTg5NCwiZXhwIjoxNjU3NDA1ODk0fQ.A5WO3M-NhDYGWkILQLVCPfv-Ve-e_Dlm1UYD2vj5UrQ' \
-H 'Content-Type: multipart/form-data' \
-F 'content=Test.' \
-F '[email ;type=image/png' \
-F 'media-description=' \
Varför fungerar inte detta?
Svar #1
REDIGERA 2: Tydligen finns det en riktigt enkel lösning på detta: Använd multer i preValidation-hooken istället för preHandler. Så, ett fungerande kodexempel skulle se ut så här:
server.register(multer.contentParser).after(() => {
server.register(
(instance: FastifyInstance, options: FastifyPluginOptions, next: HookHandlerDoneFunction) => {
instance.post(
"/create",
{
schema: {
consumes: ["multipart/form-data"],
body: {
type: "object",
properties: {
content: {
type: "string"
},
media: {
type: "string",
format: "binary"
}
}
}
},
preValidation: multer({
limits: {
fileSize: 1024 * 1024 * 5
},
storage: multer.memoryStorage()
}).single("media")
},
(request: FastifyRequest, reply: FastifyReply) => {
const content = (request.body as any).content as string;
const file = (request as any).file as File;
if (file) {
delete file.buffer;
}
reply.send({
content,
file: JSON.stringify(file) || "No file selected"
});
}
);
next();
},
{ prefix: "/posts" }
);
});
REDIGERA: Efter att jag publicerat svaret nedan lyckades jag hitta en lösning på detta. Uppdaterar mitt svar för alla andra som kan stöta på samma problem.
Först bytte jag till @fastify/multipart från fastify-multer. Sedan tog jag bort type-egenskapen från media-fältet.
media: {
format: "binary"
}
Efter detta lade jag till alternativet { addToBody: true } när jag registrerade @fastify/multipart.
import fastifyMultipart from "@fastify/multipart";
server.register(fastifyMultipart, { addToBody: true }).after(() => { ... });
Efter dessa ändringar blev fältet media tillgängligt i request.body.
GAMMALT SVAR:
Det verkar som att jag nuförtiden måste svara på mina egna frågor här. Hur som helst, jag kom fram till vad som händer. Fastifys inbyggda schemavalidering fungerar inte bra med multipart/form-data. Jag experimenterade med schemaspecifikationen för att försäkra mig om att det är så. Så jag tog bort schemavalidering från alla rutter. Mitt användningsfall här var att porta ett API från ExpressJS till Fastify, så jag hade en fin Swagger JSON-specifikation genererad med express-oas-generator liggande. Jag använde den för att generera Swagger UI och allt fungerade bra. Jag hoppas att Fastify får ordning på sig och löser det här problemet.
Svar #2
Du kan nu göra detta med @fastify/multipart genom att be det bifoga alla multipart-formulärfält till ett objekt som om det vore JSON, med hjälp av det magiska värdet "keyValues":
server.register(multipart, {
attachFieldsToBody: "keyValues",
...
});
Markera filen/filerna som typen "objekt" i schemat:
const SCHEMA_API_UPLOAD = {
consumes: [ "multipart/form-data" ],
body: {
type: "object",
properties: {
file: { type: "object" },
description: { type: "string" },
}
}
};
Då kommer varje formulärfält att vara ett fält på request.body-objektet:
server.post("/upload", { schema: SCHEMA_API_UPLOAD }, async (request, reply) => {
console.log(request.body);
});
Och du kan se objektet via curl:
curl -v localhost:8080/upload -F description="something good" -F [email
...
[20250109-20:02:50.501] INF (uy33kj8cjzw6) [127.0.0.1:48898] POST /upload
[20250109-20:02:50.502] DEB (uy33kj8cjzw6) starting multipart parsing
[20250109-20:02:50.502] TRA (uy33kj8cjzw6) Providing options to busboy
{
description: 'something good',
file: <Buffer ff d8 ff e0 ... 61483 more bytes>
}
