ivanatias
ivanatias

Reputation: 4033

What's the proper way for returning a response using Formidable on Nextjs Api?

I'm sending an uploaded file to a Next.js API route using FormData. The file is then processed on the API route using formidable and passed to sanity client in order to upload the asset, but I can't return the data to the client... I get this message in console:

API resolved without sending a response for /api/posts/uploadImage, this may result in stalled requests.

When console logging the document inside the API everything is in there, I just can't send back that response to client side. Here's my client upload function:

const addPostImage = (e) => {
    const selectedFile = e.target.files[0];

    if (
      selectedFile.type === "image/jpeg" ||
      selectedFile.type === "image/png" ||
      selectedFile.type === "image/svg" ||
      selectedFile.type === "image/gif" ||
      selectedFile.type === "image/tiff"
    ) {
      const form = new FormData();
      form.append("uploadedFile", selectedFile);
      axios
        .post("/api/posts/uploadImage", form, {
          headers: { "Content-Type": "multipart/form-data" },
        })
        .then((image) => {
          setPostImage(image);
          toast.success("Image uploaded!");
        })
        .catch((error) => {
          toast.error(`Error uploading image ${error.message}`);
        });
    } else {
      setWrongImageType(true);
    }
  };

This is my API:

import { client } from "../../../client/client";
import formidable from "formidable";
import { createReadStream } from "fs";

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async (req, res) => {
  const form = new formidable.IncomingForm();
  form.keepExtensions = true;
  form.parse(req, async (err, fields, files) => {
    const file = files.uploadedFile;
    const document = await client.assets.upload(
      "image",
      createReadStream(file.filepath),
      {
        contentType: file.mimetype,
        filename: file.originalFilename,
      }
    );
    console.log(document);
    res.status(200).json(document);
  });
};

Upvotes: 2

Views: 5212

Answers (2)

ivanatias
ivanatias

Reputation: 4033

Solution:

As stated in the comments by @juliomalves, I had to promisify the form parsing function and await its results like so:

import { client } from "../../../client/client";
import formidable from "formidable";
import { createReadStream } from "fs";

export const config = {
  api: {
    bodyParser: false,
  },
};

export default async (req, res) => {
  const form = new formidable.IncomingForm();

  form.keepExtensions = true;

  const formPromise = await new Promise((resolve, reject) => {
    form.parse(req, async (err, fields, files) => {
      if (err) reject(err);
      const file = files.uploadedFile;
      const document = await client.assets.upload(
        "image",
        createReadStream(file.filepath),
        {
          contentType: file.mimetype,
          filename: file.originalFilename,
        }
      );
      resolve(document);
    });
  });

  res.json(formPromise);
}; 

Then I checked for the response's status on the client-side.

Upvotes: 2

Ablomis
Ablomis

Reputation: 167

Your code is not working because by default formidable saves files to disk, which is not available on vercel. This works.

        const chunks = []
        let buffer;

        const form = formidable({
          fileWriteStreamHandler: (/* file */) => {
            const writable = new Writable();
            // eslint-disable-next-line no-underscore-dangle
            writable._write = (chunk, enc, next) => {
              chunks.push(chunk);
              next();
            };
            return writable;
          },
        })

        form.parse(req, (err, fields) => {
          if (err) {
            res.end(String(err));
            return;
          }
          buffer = Buffer.concat(chunks);
          res.end();
        });

Upvotes: 0

Related Questions