I'm new to Fastify and backend development. I'm trying to upload an image to Cloudinary using my Fastify server for the first time. The upload_stream method takes two arguments. The first one is an options object, the second is a callback function that must be executed after the image is uploaded.
async function uploadImage(request: FastifyRequest, reply: FastifyReply) {
const pipeline = util.promisify(stream.pipeline);
const data = await request.file();
if(!data) {
return reply.code(400).send("no data")
}
const randomFilename = crypto.randomBytes(30).toString('hex');
await pipeline(
data.file,
server.cloudinary.uploader.upload_stream(
{ public_id: randomFilename, folder: 'library' },
(error, result) => {
if(result) {
console.log(result);
reply.code(201).send({
meta: {
code: 201,
message: result?.url
}
});
} else {
reply.code(400).send({
meta: {
code: 400,
message: error
}
});
}
})
);
}
The image loads to Cloudinary and console.log(result);
in a callback function prints a result object in my console. That's fine.
But when I'm trying to pass an uploaded image's link to the response object, it says that the reply has already been sent with a status code 200. The full error message is below. As you migh see, I don't even have a response with status code 200 in my code.
WARN (17040): Reply already sent
reqId: "req-1"
err: {
"type": "FastifyError",
"message": "Reply was already sent.",
"stack":
FastifyError: Reply was already sent.
at _Reply.Reply.send (C:\...\fastify-test\node_modules\fastify\lib\reply.js:127:26)
at C:\...\fastify-test\src\api\user\controller.ts:133:29
at C:\...\fastify-test\node_modules\cloudinary\lib\utils\index.js:1275:12
at IncomingMessage.<anonymous> (C:\...\fastify-test\node_modules\cloudinary\lib\uploader.js:509:9)
at IncomingMessage.emit (node:events:525:35)
at IncomingMessage.emit (node:domain:489:12)
at endReadableNT (node:internal/streams/readable:1359:12)
at processTicksAndRejections (node:internal/process/task_queues:82:21)
"name": "FastifyError",
"code": "FST_ERR_REP_ALREADY_SENT",
"statusCode": 500
}
The question is how to send a link of an uploaded image as a server response.
The issue is the async
handler.
Since it is an async function, what it returns is sent as the HTTP response.
In fact, if you add return 'foo'
after the await pipeline()
I would expect your client will get that string.
What is happening is that the pipeline
function ends before the cloudinary.uploader.upload_stream
function executes the callback.
Welcome to Node.js and streams 😈
So the uploadImage
returns undefined
and the callback that handle the reply
object runs after the response has been sent.
There are plenty some solution here
return reply
after the pipeline execution - this will force fastify to not submit the handler resultsawait pipeline(..)
return reply
await new Promise((resolve, reject) => {
pipeline(
data.file,
server.cloudinary.uploader.upload_stream(
{ public_id: randomFilename, folder: 'library' },
(error, result) => {
if(result) {
console.log(result);
reply.code(201).send({
meta: {
code: 201,
message: result?.url
}
});
resolve()
} else {
reply.code(400).send({
meta: {
code: 400,
message: error
}
});
reject()
}
})
);
})
Further reading: https://www.fastify.io/docs/latest/Reference/Routes/#async-await
This is explained even more on my Fastify book - checkout my profile for more info 😄