I just want to know is it possible to upload files using server actions to the nextjs project's some folder maybe public folder like storing images, docs, sheets and pdf stuff.
In nodejs we have multer and formidable but I am stuck with server actions because it doesn't use
'Content-Type': 'multipart/form-data
I need to store applicants like name, email, phone and resume. I want to know is it possible with server actions.
"use server";
import prisma from "@/utils/prisma";
import fs from "fs";
import FormData from "form-data";
import path from "path";
import { v4 as uuid } from "uuid";
const uploadApplicant = async (
name: string,
email: string,
tel: string,
file: File | null
) => {
const fileName = uuid();
const formData = new FormData();
formData.append("file", file);
console.log("Form data: ", formData);
const binaryData = formData.getBuffer();
console.log("Binary: ");
console.log(binaryData);
const filePath = path.join(
__dirname,
"../../../../",
"public",
"uploads",
`${fileName}.pdf`
);
fs.writeFileSync(filePath, binaryData, "binary");
await prisma.applicant.create({
data: {
name,
tel,
email,
fileName,
},
});
};
export default uploadApplicant;
You'll have to grab the File
object from the binary FormData
instance that gets passed to the server action. Try this:
'use server';
import { writeFile } from 'fs/promises';
import { join } from 'path';
export async function upload(data: FormData) {
const file: File | null = data.get('file') as unknown as File;
if (!file) throw new Error('No file uploaded');
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
const path = join(process.cwd(), file.name);
await writeFile(path, buffer);
console.log(`open ${path} to see the uploaded file`);
return { success: true };
}
If you need to upload very large files, I recommend to use TUS instead of server actions. This way you can chunk the upload to smaller sizes and have neat features like seeing the progress of upload and auto-retry if the user's network experiences interruptions.
All you need is /app/api/upload/[[...slug]]/route.s
with
import { FileStore } from '@tus/file-store';
import { Server } from '@tus/server';
import { env } from '@/env.mjs';
import { auth } from '@/lib/auth';
const server = new Server({
path: '/api/upload',
datastore: new FileStore({ directory: env.VIDEOS_DIR })
// async onIncomingRequest(req) {
// const session = await auth();
// if (!session) {
// throw { status_code: 401, body: 'Unauthorized' };
// }
// console.log(session);
// }
});
const handler = server.handleWeb.bind(server);
export {
handler as GET,
handler as POST,
handler as PATCH,
handler as DELETE,
handler as OPTIONS,
handler as HEAD
};
and the tus-js-client
to use it on the client:
'use client';
// create a large test file with dd if=/dev/zero of=test.mp4 bs=1G count=10
import { useState } from 'react';
import { Upload } from 'tus-js-client';
export default function UploadPage() {
const [progress, setProgress] = useState<number>(0);
const handleUpload = (file: File) => {
const upload = new Upload(file, {
endpoint: '/api/upload',
chunkSize: 50 * 1024 * 1024,
onProgress: (bytesUploaded, bytesTotal) => {
const percentage = (bytesUploaded / bytesTotal) * 100.0;
setProgress(percentage);
},
onError: (error) => {
console.error('Upload failed:', error);
},
onSuccess: (payload) => {
console.log('File uploaded successfully!');
}
});
upload.start();
};
return (
<div style={{ padding: '20px' }}>
<h3>Upload a File using TUS</h3>
<input
type='file'
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
handleUpload(file);
}
}}
/>
<div style={{ marginTop: '20px' }}>
<progress value={progress} max={100} style={{ width: '100%' }} />
<p>{Math.round(progress)}% uploaded</p>
</div>
</div>
);
}