skip to Main Content

SOLVED / SEE MY OWN ANSWER FOR DETAILS

Hey I am getting this very strange error and I can’t seem to be able to figure it out. So basically ‘m trying to send an audio recording that the user records in my Nextjs Frontend, to my Nextjs API. Which seems to be going well. But, when I try to forward the entire recording from my Nextjs api to my actual backend endpoint which is running Node/Express.js on Vercel, I end up getting a 405 bad request error. Any ideas?

IMPORTANT NOTE: Everything worked fine on localhost. But not when I took it to production on Vercel. (Both client and server are Node apps running on Vercel. One is Nextjs, a slightly older version, and the server is Express)

I am using Next.js as an API in the middle for security reasons

My only theory so far is that "formData.append" is not working properly as whenever i try to log it or its entires out, i always get a blank object: {}

Here:

const formDataToSend = new FormData()

 formDataToSend.append(fileName, new Blob([readResult], { type: 'audio/wav' }), `${fileName}.wav`)

(full code given down below)

Nextjs version: 13.5.4
Expressjs version: 4.18.2

EXPRESS.JS CODE:

app.use(cors()) 

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, '/tmp/') 
  },
  filename: function (req, file, cb) {
    cb(null, `${file.originalname}`)
  }
})

const upload = multer({ storage: storage })


...


app.post('/upload', [requireSession, upload.any()], async (req, res) =>
{
  // the request never even gets here in the first place
  // requireSession works fine
})

NEXT.JS API:

import { csrf } from '../../../../lib/csrf'
import axios from 'axios'
import crypto from 'crypto'
import multiparty from 'multiparty'
import fs from 'fs'

// valid url
const ENDPOINT_URL = 'https://my-api.com/upload'

// works fine
async function readAudioFile(filePath) {
  return new Promise((resolve, reject) => {
    const readStream = fs.createReadStream(filePath)
    const chunks = []
    readStream.on('data', (chunk) => {
      chunks.push(chunk)
    })
    readStream.on('end', () => {
      const fileBuffer = Buffer.concat(chunks)
      resolve(fileBuffer)
    })
    readStream.on('error', (err) => {
      reject(err)
    })
  })
}

async function post(req, res)
{
  try {
    // works fine
    const form = new multiparty.Form()

    // works fine
    const formData = await new Promise((resolve, reject) => {
      form.parse(req, function (err, fields, files) {
        if (err) reject({ err })
        resolve({ fields, files })
      })
    })

    // works fine
    const readResult = await readAudioFile(formData.files.audio[0].path)

    // works fine
    const randomizer = `${req?.headers?._uuid}-${req?.query?.taskId}-${(new Date().getTime()).toString(16)}`

    // works fine
    const fileName = crypto.createHash('sha256').update(randomizer).digest('hex')

    // works fine
    const formDataToSend = new FormData()

    // !! Not sure. when i log em out, "formDataToSend" and "formDataToSend.entries()" are both empty. (Just a "{}")
    formDataToSend.append(fileName, new Blob([readResult], { type: 'audio/wav' }), `${fileName}.wav`)

    // Nothing undefined here, works fine
    const headers = { 
      'client': process.env.API_ACCESS_KEY, 
      '_session': req.headers._session,
      '_uuid': req.headers._uuid,
      ...req.headers,
      'Content-Type': 'multipart/form-data', 
      'Request-Index': '0' ,
    }
  
    // this request fails with code 405
    await axios.post(ENDPOINT_URL, formDataToSend, { headers, params: { ...req.query } })
    .then(response => (res.status(200).json(response.data)))
    .catch(error => (console.log(error), res.status(500).json({
      ...getErrorMessage(),
      errorDetails: error,
      dataToSend
    })))
  }
  catch (error) {
    console.log(error)
    res.status(501).json({
      ...getErrorMessage(),
      errorDetails: error,
      dataToSend
    })
  }
}

async function handler(req, res) 
{
  switch (req.method.toLowerCase())
  {
    case 'post':
      await post(req, res)
      break
    default:
      res.status(200).send(`Method ${req.method} is not allowed.`)
      break
  }
}

const getErrorMessage = () => { return { time: Date.now(), result: false, status: 500, message: 'Something went wrong' } }

export default csrf(handler)

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

I’ve already tried pretty much everything i can think of. Including using fetch instead of axios, messed with the CORS settings of my express.js endpoint, etc. I think the problem is in FormData somehow. Worked fine in localhost though

3

Answers


  1. Chosen as BEST ANSWER

    Thanks for the help guys. Here's how I solved it:

    changed this:

        const headers = { 
          'client': process.env.API_ACCESS_KEY, 
          '_session': req.headers._session,
          '_uuid': req.headers._uuid,
          ...req.headers,
          'Content-Type': 'multipart/form-data', 
          'Request-Index': '0' ,
        }
    

    to this:

    const headers = { 
      'client': process.env.API_ACCESS_KEY, 
      '_session': req.headers._session,
      '_uuid': req.headers._uuid,
      'Content-Type': 'multipart/form-data', 
      'Request-Index': '0' ,
    }
    

    Apparently I forgot to remove a single line of code that was interfering with the request headers. Just realized what was going on by coincidence after wasting a couple nights tryna get this thing to work. Silly me!


  2. Try After Changing This

    formDataToSend.append(fileName, new Blob([readResult], { type: 'audio/wav' }), `${fileName}.wav`)
    

    To This

    .append() // dosen't take any third parameter
    
    formDataToSend.append(fileName, new Blob([readResult], { type: 'audio/wav' }))
    
    Login or Signup to reply.
  3. Since, Vercel is Serverless computing provider. I think you should try to deploy your web application to other platforms. e.g Railway

    You can also read this: https://vercel.com/guides/using-express-with-vercel

    Let me know if this works.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search