I'm trying to upload binary data with the latest version of Next.js, at the time of writing is 13.4.9. I've tried busboy and multer to no avail. It seems the Next request object is a bit different than the Node Request object, so the online examples are no longer working, and I don't want to write a custom express server...
Here's what I have; there are no errors, but multer is not reading the uploaded file or textual data:
Client
'use client';
import { useRef } from 'react'
export default function SampleUpload() {
const fileRef = useRef(null);
return (
<div>
<input id="fileInput" type="file" name="file" ref={fileRef} />
<button onClick={() => {
const formData = new FormData();
formData.append('audio', fileRef.current.files[0]);
const jsonData = {
sampleRate: 44100,
};
formData.append("data", JSON.stringify(jsonData));
fetch('/api/upload', {
method: 'POST',
body: formData,
}).then(response => {
if (response.ok) {
return response.json();
}
throw new Error('Network response was not ok.');
}).then(data => {
console.log(data);
}).catch(error => {
console.error('There was a problem with the fetch operation:', error.message);
});
}}>Upload</button>
</div>
)
}
Server
import { NextResponse } from 'next/server';
const multer = require('multer');
const storage = multer.memoryStorage(); // store the file as buffer in memory
const upload = multer({ storage: storage });
// This produces a warning, but I can't find the appropriate way to do it here
// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config
export const config = {
api: {
bodyParser: false,
},
};
export async function POST(req: Request) {
const res = new NextResponse('success', { status: 200 });
await new Promise((resolve, reject) => {
upload.single('audio')(req, res, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
// There are no errors, but neither req.file nor req.body are set by now...
console.log('file? ', req.file)
console.log('body? ', req.body)
return res;
}
It looks like the experimental Server Actions feature is the only way to achieve this now, because you can no longer override the Next server. Tune the accept
attribute to your needs.
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
},
};
module.exports = nextConfig;
Client
'use client'
import uploadFile from '@/actions/uploadFile';
export default function FileUploader() {
return (
<form action={uploadFile}>
<div>Choose an audio file:</div>
<input type="file" id="myFile" name="myFile" accept="audio/*" />
<input type="submit" value="Upload" />
</form>);
}
Server
'use server'
export default async function uploadFile(formData: FormData) {
const _formData = {};
// _formData.myFile holds the file contents
for(let [key, value] of formData.entries()) {
_formData[key] = value;
}
}