node.jstypescriptnext.jsckeditor5formidable

NextJs 13 API to handle uploaded files does not work using formidable


I have a NextJs 13 project that requires and API which can handle a file that is sent to it. I have used formidable for handling the middleware

My app/api/uploadfile/route.ts file (code has been minimized for concentration)

import { IncomingForm } from "formidable";

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

const asyncParse = (req:any) =>
  new Promise((resolve, reject) => {
    const form = new IncomingForm({ multiples: true });
    form.parse(req, (err, fields, files) => {
      if (err) return reject(err);
      resolve({ fields, files });
    });
  });

export async function PUT(req:any, res:any) {
  console.log("Receiving");
  if (req.method === "PUT") {
    const result = await asyncParse(req);
    res.status(200).json({ result });
  }
}

Here i call the api using the endpoint localhost:3000/api/uploadfile via postman Postman SS for reference

I get an error

error - TypeError: req.on is not a function
    at IncomingForm.parse (webpack-internal:///(sc_server)/./node_modules/formidable/lib/incoming_form.js:90:9)
    at eval (webpack-internal:///(sc_server)/./app/api/uploadfile/route.ts:18:14)
    at new Promise (<anonymous>)
    at asyncParse (webpack-internal:///(sc_server)/./app/api/uploadfile/route.ts:14:27)
    at PUT (webpack-internal:///(sc_server)/./app/api/uploadfile/route.ts:29:30)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/server/future/route-modules/app-route/module.js:219:24)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/server/lib/trace/tracer.js:92:36)
    at NoopContextManager.with (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/@opentelemetry/api/index.js:360:30)
    at ContextAPI.with (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/@opentelemetry/api/index.js:30:58)
    at NoopTracer.startActiveSpan (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/@opentelemetry/api/index.js:953:34)
    at ProxyTracer.startActiveSpan (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/@opentelemetry/api/index.js:993:36)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/server/lib/trace/tracer.js:81:107)
    at NoopContextManager.with (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/@opentelemetry/api/index.js:360:30)
    at ContextAPI.with (webpack-internal:///(sc_server)/./node_modules/next/dist/compiled/@opentelemetry/api/index.js:30:58)
    at NextTracerImpl.trace (webpack-internal:///(sc_server)/./node_modules/next/dist/server/lib/trace/tracer.js:81:32)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/server/future/route-modules/app-route/module.js:214:49)
    at AsyncLocalStorage.run (node:async_hooks:345:14)
    at Object.wrap (webpack-internal:///(sc_server)/./node_modules/next/dist/server/async-storage/static-generation-async-storage-wrapper.js:32:24)
    at eval (webpack-internal:///(sc_server)/./node_modules/next/dist/server/future/route-modules/app-route/module.js:171:93)
    at AsyncLocalStorage.run (node:async_hooks:345:14)
    at Object.wrap (webpack-internal:///(sc_server)/./node_modules/next/dist/server/async-storage/request-async-storage-wrapper.js:59:24)
    at AppRouteRouteModule.execute (webpack-internal:///(sc_server)/./node_modules/next/dist/server/future/route-modules/app-route/module.js:170:87)
    at AppRouteRouteModule.handle (webpack-internal:///(sc_server)/./node_modules/next/dist/server/future/route-modules/app-route/module.js:259:41)
    at RouteHandlerManager.handle (E:\Projects\Templates\backend-template\node_modules\next\dist\server\future\route-handler-managers\route-handler-manager.js:22:29)
    at doRender (E:\Projects\Templates\backend-template\node_modules\next\dist\server\base-server.js:848:58)
    at cacheEntry1.responseCache.get.incrementalCache.incrementalCache (E:\Projects\Templates\backend-template\node_modules\next\dist\server\base-server.js:1059:34)
    at E:\Projects\Templates\backend-template\node_modules\next\dist\server\response-cache\index.js:83:42
    at ResponseCache.get (E:\Projects\Templates\backend-template\node_modules\next\dist\server\response-cache\index.js:131:11)
    at DevServer.renderToResponseWithComponentsImpl (E:\Projects\Templates\backend-template\node_modules\next\dist\server\base-server.js:978:54)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Things i have tried

  1. Using multer as an alternative(weird artefacts with NextJS 13 and typescript)(have tried various implementations)
  2. Using POST and GET in place of PUT in the code example
  3. Googling the error and also using chatgpt
  4. Changing versions of formidable to v2/v3 and also to v1.2.2 the outcome is the same
  5. Better code to upload to my prisma db(but it fails in the calling the api step itself)

My end goal

I want to use this API endpoint in my ckeditor5 implementation(as the image upload adapter)


Solution

  • You don't need formidable anymore for it. Next.js 13 added a new feature formData(); to their application that handles file upload and they restricted the use of formidable which handles this functionality. So this is it (I'm answering your goal: using CKEditor5 to handle this. Might work using postman as well):

      const formData = await request.formData();
      const file: any = formData.get("upload");
    

    for me I needed the file to be uploaded on cloudinary so this is how I solved it.

     const formData = await request.formData();
        const file: any = formData.get("upload");
        
        const arrayBuffer = await file.arrayBuffer();
        const buffer = new Uint8Array(arrayBuffer);
    
        let uploadedImageUrl: any = await new Promise((resolve, reject) => {
          cloudinary.uploader
            .upload_stream({}, function (error, result: any) {
              if (error) {
                reject(error);
                return;
              }
    
              resolve(result);
            })
            .end(buffer);
        });
    

    If you console.log(result) you will see the returned value of your file. return the secure_url and it will return the image into the editor.