I needed to upload a file from a browser to middleware through an API call, and then have the middleware also send said file to a service through an API call. The service expects the payload to be exactly the file data, no enveloping.
I own the middleware and client code and here’s the solution that I set up to correctly call the service API. I didn’t conduct thorough research on this approach but it worked for me.
The popular solution for handling file upload in Next.js land is a Multer middleware with Next-Connect for simplified integration. For simplicity, I opted to just use Busboy to process the body payload. As I discovered, it turns out that Multer also leverages Busboy.
The Client
I’m expecting the client to be a modern browser so I can presume the fetch interface to be present. I might use Axios in the client to streamline with Node server coding as a future consideration. To bundle the image in my API call, I use FormData for convenience. I’ve discussed using FormData as an AJAX version of submitting a form and it is well supported.
// get the File object from a file uploader event
const file = e.target.files[0]
// use FormData to build the payload as multipart/form-data
const blobData = new FormData()
blobData.append('image', file)
// make api call with image payload
fetch(route, {
method: 'POST',
body: blobData,
})
The Middleware
The middleware runs on a Next.js server. For this example, assume the existence of the req argument. Next.js automatically processes the request body so I had to disable that to allow for Busboy to stream the data.
I encountered a challenge in extracting a correctly encoded image data payload in isolation. Through experimentation I discovered that Axios knows what to do with a Buffer as a data argument.
Code updated to Busboy 1.0 compatibility – see breaking changes.
// don't process the body - busboy will handle that
export const config = {
api: {
bodyParser: false
}
}
// process payload inside a request handler
// configure busboy to only look for one file
const busboy = Busboy({
headers: req.headers,
limits: {
files: 1
}
})
// process the request body and stream the file data
busboy.on("file", (fieldname, file, {filename, encoding, mimetype}) => {
const buf = []
file.on("data", (d) => {
buf.push(d)
).on("end", async () => {
const data = Buffer.concat(buf)
axios.post('/api-route', data, {headers: {'Content-Type': 'image/*'}})
})
})
// pipe the request through the busboy middleware
req.pipe(busboy)