Search code examples
reactjstypescriptaxiosnext.jsmultipartform-data

Uploading file to a server via the next js api route


I am using the Next.js api as a middleware before transferring the requests to the server, I am trying to send a multipart/formdata request with a file, it works if I call the backend API directly from the client-side with a FormData object, I wrote the Next API to parse that form data, form a new form data (server side this time) and call the backend API but that fails.

Here is the code:

import axios from "axios";
import formidable from "formidable";
import FormData from "form-data";
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
//
import BlogAPIs from "utils/apis/BlogAPIs";

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

export default async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const session = await getSession({ req });

    const formData = new FormData();

    const fs = require("fs");

    const data: { fields: any; files: any } = await new Promise(
      (resolve, reject) => {
        const form = new formidable.IncomingForm();

        form.parse(req, (err: any, fields: any, files: any) => {
          if (err) reject({ err });
          resolve({ fields, files });
        });
      }
    );

    ["title", "content", "description", "thumbnail"].map((key) => {
      data.fields[key] && formData.append(key, data.fields[key]);
      data.files[key] &&
        formData.append(key, fs.createReadStream(data.files[key].filepath));
    });

    let config = {
      method: "post",
      url: `${process.env.API_BASE_URL}/blogs/`,
      headers: {
        Authorization: `Bearer ${session?.backendToken as string}`,
        ...formData.getHeaders(),
      },
      data: formData,
    };

    await axios(config);

    res.status(200).json("Succesfully added blog");
  } catch (error: any) {
    res.status(700).json(error.message);
  }
};

I can't seem to figure out what I am doing wrong here...


Solution

  • This is how I was able to achieve it:

    import axios from "axios";
    import formidable from "formidable";
    import type { NextApiRequest, NextApiResponse } from "next";
    import { getSession } from "next-auth/react";
    import { processError } from "utils/apis/processError";
    //
    
    export const config = {
      api: {
        bodyParser: false,
      },
    };
    
    export default async (req: NextApiRequest, res: NextApiResponse) => {
      try {
        const FormData = require("form-data");
        const concat = require("concat-stream");
        const fs = require("fs");
    
        const session = await getSession({ req });
    
        const data: { fields: any; files: any } = await new Promise(
          (resolve, reject) => {
            const form = new formidable.IncomingForm({
              keepExtensions: true,
            });
    
            form.parse(req, (err: any, fields: any, files: any) => {
              if (err) reject({ err });
              resolve({ fields, files });
            });
          }
        );
    
        const promise = new Promise<{ data: any; headers: any }>((resolve) => {
          const formData = new FormData();
          ["title", "content", "description", "tags"].map((key) => {
            data.fields[key] && formData.append(key, data.fields[key]);
          });
    
          data.files["thumbnail"] &&
            formData.append(
              "thumbnail",
              fs.createReadStream(data.files["thumbnail"].filepath),
              data.files["thumbnail"].originalFilename
            );
    
          formData.pipe(
            concat({ encoding: "buffer" }, (data: any) =>
              resolve({ data, headers: formData.getHeaders() })
            )
          );
        });
    
        promise
          .then(({ data, headers }) =>
            axios.post(`${process.env.API_BASE_URL}/blogs/`, data, {
              headers: {
                Authorization: `Bearer ${session?.backendToken as string}`,
                ...headers,
              },
            })
          )
          .catch((error) => {
            const errorMessage = processError(error);
            res.status(700).json(errorMessage);
          })
          .then((response) => {
            res.status(200).json({
              slug: response?.data.slug,
            });
          });
      } catch (error: any) {
        res.status(700).json(error.message);
      }
    };
    

    I created a promise with the streaming data and then sent the same to my server.