8
Fullstack

Media Uploader

A full-stack media upload component with drag-and-drop functionality, progress tracking, and cloud storage integration.

Media Uploader

A full-stack media upload component with drag-and-drop functionality, progress tracking, and cloud storage integration. Built with Next.js server actions and Cloudinary for seamless media management.

Media Uploader Component

Loading component registry...

Installation

Install the component using the AxionsJS CLI:

npx axionjs-ui add media-uploader

Configure environment variables in your .env file:

DATABASE_URL="your_database_url"
CLOUDINARY_CLOUD_NAME="your_cloud_name"
CLOUDINARY_API_KEY="your_api_key"
CLOUDINARY_API_SECRET="your_api_secret"

Make sure to keep your Cloudinary credentials secure and never expose them in client-side code.

Database Schema

The component uses Prisma with the following schema. Add this to your schema.prisma file:

schema.prisma
model Media {
  id           String    @id @default(cuid())
  name         String
  size         Int
  url          String
  publicId     String?
  thumbnailUrl String?
  mediaType    MediaType
  createdAt    DateTime  @default(now())
  updatedAt    DateTime  @updatedAt
}
 
enum MediaType {
  IMAGE
  VIDEO
}

Run npx prisma db push or npx prisma migrate dev after adding the schema to apply changes to your database.

Usage

Import and use the MediaUploader component:

app/upload/page.tsx
import { MediaUploader } from "@/components/media-uploader";
 
export default function UploadPage() {
  return (
    <div className="container mx-auto py-10">
      <h1 className="text-3xl font-bold mb-6">Upload Media</h1>
      <MediaUploader />
    </div>
  );
}

Server Actions

The component includes three main server actions for handling media operations:

Upload Media

actions/media-actions.ts
export async function uploadToCloudinary({
  name,
  type,
  size,
  base64,
  isVideo,
}: {
  name: string;
  type: string;
  size: number;
  base64: string;
  isVideo: boolean;
}) {
  const result = await cloudinary.uploader.upload(
    `data:${type};base64,${base64}`,
    {
      public_id: `upload_${Date.now()}_${name}`,
      folder: isVideo ? "videos" : "images",
      resource_type: isVideo ? "video" : "image",
    }
  );
 
  const mediaRecord = await db.media.create({
    data: {
      name,
      size,
      url: result.secure_url,
      publicId: result.public_id,
      thumbnailUrl: isVideo ? null : result.secure_url,
      mediaType: isVideo ? "VIDEO" : "IMAGE",
    },
  });
 
  return mediaRecord;
}

Get Media

actions/media-actions.ts
export async function getMedia() {
  const media = await db.media.findMany({
    orderBy: {
      createdAt: "desc",
    },
  });
 
  return media;
}

Delete Media

actions/media-actions.ts
export async function deleteMedia(id: string) {
  const media = await db.media.findUnique({
    where: { id },
  });
 
  if (!media) {
    throw new Error("Media not found");
  }
 
  // Delete from Cloudinary
  if (media.publicId) {
    await cloudinary.uploader.destroy(media.publicId, {
      resource_type: media.mediaType === "VIDEO" ? "video" : "image",
    });
  }
 
  // Delete from database
  await db.media.delete({
    where: { id },
  });
 
  return { success: true };
}

Component Features

The MediaUploader component provides:

  • Drag and Drop Interface: Intuitive file dropping area
  • Multiple File Selection: Upload multiple files simultaneously
  • Progress Tracking: Real-time upload progress indicators
  • Error Handling: Comprehensive error handling with retry functionality
  • File Validation: Client-side file type and size validation

API Reference

MediaUploader Props

PropTypeDefault
className
string
-
maxFiles
number
-
maxFileSize
number
-
onComplete
() => void
-

MediaGallery Props

PropTypeDefault
media
Media[]
-
columns
number
-
onDelete
(id: string) => void
-

Media Object Type

interface Media {
  id: string;
  name: string;
  url: string;
  thumbnailUrl: string | null;
  mediaType: "IMAGE" | "VIDEO";
  size: number;
  createdAt: Date;
  updatedAt: Date;
}

Always implement proper error handling for production applications.

Best Practices

Server-side Validation Always validate files on the server:

const validateFile = (file: UploadData) => {
  const allowedMimes = ['image/jpeg', 'image/png', 'video/mp4'];
  const maxSize = 50 * 1024 * 1024; // 50MB
  
  if (!allowedMimes.includes(file.type)) {
    throw new Error('Invalid file type');
  }
  
  if (file.size > maxSize) {
    throw new Error('File too large');
  }
};

Environment Security

  • Never expose Cloudinary credentials in client code
  • Use environment variables for all sensitive data
  • Implement proper CORS policies

Conclusion

The Media Uploader component is a powerful and flexible solution for managing media uploads in full-stack applications. It integrates seamlessly with Next.js, Prisma, and Cloudinary, providing a robust set of features including drag-and-drop uploads, progress tracking, and error handling. The modular design allows for easy customization and extension, making it suitable for a wide range of applications from simple blogs to complex media management systems.

On this page