Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Server Actions and files

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;
like image 727
Ismoiljon Abduqahhorov Avatar asked Jun 07 '26 07:06

Ismoiljon Abduqahhorov


2 Answers

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.DIR_TO_STORE_FILES })

  // 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 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>
  );
}
like image 85
darksky Avatar answered Jun 10 '26 10:06

darksky


File objects are a web API that are only available in the browser environment, not in Node.js.

When you need to upload files to your server, you typically don't send the entire File object to the server. Instead, you would send the file data as a binary stream(array buffer). This is usually done using a FormData object, which can handle file uploads. or you should handle the file upload on the client-side.

like image 41
Lakmal Avatar answered Jun 10 '26 11:06

Lakmal



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!