Skip to main content

File Storage

Upload files as part of your documents and user profiles with automatic S3 storage integration. Files are uploaded alongside your data and their URLs are automatically stored in your documents.
Cocobase integrates file uploads directly into document and user operations. Files are automatically uploaded to S3 storage and their URLs are saved in your documents.

Overview

File storage features:
  • Integrated uploads - Upload files with document creation/updates
  • Multiple file types - Images, documents, videos, any file type
  • Single or array - Upload single files or arrays of files
  • Automatic URLs - File URLs automatically saved in documents
  • S3 storage - Secure, scalable cloud storage
  • User profile files - Upload avatars and cover photos with registration

Upload Files with Documents

Upload files when creating or updating documents using field naming.

Single File Upload

import { Cocobase } from 'cocobase';

const db = new Cocobase({ apiKey: 'your-api-key' });

// Upload avatar with user profile
const user = await db.createDocumentWithFiles(
  'users',
  {
    name: 'John Doe',
    email: '[email protected]',
    bio: 'Software developer'
  },
  {
    avatar: avatarFile,          // Single file
    cover_photo: coverPhotoFile  // Another single file
  }
);

console.log('Avatar URL:', user.data.avatar);
console.log('Cover URL:', user.data.cover_photo);
Response Structure:
{
  "id": "user-123",
  "collection": "users",
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:30:00Z",
  "data": {
    "name": "John Doe",
    "email": "[email protected]",
    "bio": "Software developer",
    "avatar": "https://storage.cocobase.buzz/.../avatar.jpg",
    "cover_photo": "https://storage.cocobase.buzz/.../cover.jpg"
  }
}

Multiple Files (Array)

Upload multiple files to create an array of URLs:
// Product with gallery
const product = await db.createDocumentWithFiles(
  'products',
  {
    name: 'Laptop',
    price: 1299,
    description: 'High-performance laptop'
  },
  {
    main_image: mainImageFile,
    gallery: [img1File, img2File, img3File]  // Array of files
  }
);

console.log('Main image:', product.data.main_image);
console.log('Gallery:', product.data.gallery);
// Gallery: ["https://.../img1.jpg", "https://.../img2.jpg", "https://.../img3.jpg"]
Response:
{
  "id": "product-123",
  "data": {
    "name": "Laptop",
    "price": 1299,
    "description": "High-performance laptop",
    "main_image": "https://storage.cocobase.buzz/.../main.jpg",
    "gallery": [
      "https://storage.cocobase.buzz/.../img1.jpg",
      "https://storage.cocobase.buzz/.../img2.jpg",
      "https://storage.cocobase.buzz/.../img3.jpg"
    ]
  }
}

Update Documents with Files

Update existing documents with new files:
// Update only the avatar
await db.updateDocumentWithFiles(
  'users',
  'user-123',
  undefined,  // No data changes
  {
    avatar: newAvatarFile
  }
);

// Update both data and files
await db.updateDocumentWithFiles(
  'users',
  'user-123',
  { bio: 'Updated bio' },  // Data changes
  {
    avatar: newAvatarFile,
    cover_photo: newCoverFile
  }
);

User Registration with Files

Register users with profile pictures:
// Register with avatar
await db.registerWithFiles(
  '[email protected]',
  'password123',
  {
    username: 'johndoe',
    full_name: 'John Doe',
    bio: 'Developer'
  },
  {
    avatar: avatarFile
  }
);

// Get user details
const user = await db.auth.getUser();
console.log('Avatar URL:', user.avatar);
Response:
{
  "id": "user-123",
  "email": "[email protected]",
  "username": "johndoe",
  "full_name": "John Doe",
  "bio": "Developer",
  "avatar": "https://storage.cocobase.buzz/.../avatar.jpg",
  "createdAt": "2024-01-15T10:00:00Z"
}

Update User Profile with Files

Update authenticated user’s profile pictures:
// Update only avatar
await db.updateUserWithFiles(
  undefined,
  undefined,
  undefined,
  { avatar: newAvatarFile }
);

// Update bio and avatar
await db.updateUserWithFiles(
  { bio: 'Updated bio', location: 'New York' },
  undefined,
  undefined,
  { avatar: newAvatarFile }
);

// Update email, data, and files
await db.updateUserWithFiles(
  { username: 'newusername' },
  '[email protected]',
  undefined,
  {
    avatar: newAvatar,
    cover_photo: newCover
  }
);

React Example

Complete React component with file uploads:
import React, { useState } from 'react';
import { Cocobase } from 'cocobase';

const db = new Cocobase({ apiKey: 'your-api-key' });

function SignUpForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [username, setUsername] = useState('');
  const [avatarFile, setAvatarFile] = useState<File | null>(null);
  const [avatarPreview, setAvatarPreview] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (file) {
      // Validate file
      if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
        alert('Please upload a valid image file');
        return;
      }

      if (file.size > 5 * 1024 * 1024) { // 5MB
        alert('File size must be less than 5MB');
        return;
      }

      setAvatarFile(file);

      // Show preview
      const reader = new FileReader();
      reader.onloadend = () => {
        setAvatarPreview(reader.result as string);
      };
      reader.readAsDataURL(file);
    }
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      await db.registerWithFiles(
        email,
        password,
        { username },
        avatarFile ? { avatar: avatarFile } : undefined
      );

      // Get user details
      const user = await db.auth.getUser();
      console.log('User created:', user);
      alert('Sign up successful!');
    } catch (error) {
      console.error('Sign up failed:', error);
      alert('Sign up failed. Please try again.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="max-w-md mx-auto p-6">
      <h2 className="text-2xl font-bold mb-6">Create Your Account</h2>

      <form onSubmit={handleSubmit} className="space-y-4">
        {/* Avatar Preview */}
        {avatarPreview && (
          <div className="flex justify-center mb-4">
            <img
              src={avatarPreview}
              alt="Avatar preview"
              className="w-24 h-24 rounded-full object-cover"
            />
          </div>
        )}

        {/* Avatar Upload */}
        <div>
          <label className="block text-sm font-medium mb-2">
            Profile Picture
          </label>
          <input
            type="file"
            accept="image/*"
            onChange={handleAvatarChange}
            className="w-full"
          />
        </div>

        <input
          type="text"
          placeholder="Username"
          value={username}
          onChange={(e) => setUsername(e.target.value)}
          required
          className="w-full px-3 py-2 border rounded"
        />

        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
          className="w-full px-3 py-2 border rounded"
        />

        <input
          type="password"
          placeholder="Password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
          className="w-full px-3 py-2 border rounded"
        />

        <button
          type="submit"
          disabled={loading}
          className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:bg-gray-400"
        >
          {loading ? 'Creating Account...' : 'Sign Up'}
        </button>
      </form>
    </div>
  );
}

Create products with multiple images:
import React, { useState } from 'react';
import { Cocobase } from 'cocobase';

const db = new Cocobase({ apiKey: 'your-api-key' });

function CreateProductForm() {
  const [name, setName] = useState('');
  const [price, setPrice] = useState('');
  const [mainImage, setMainImage] = useState<File | null>(null);
  const [galleryImages, setGalleryImages] = useState<File[]>([]);
  const [loading, setLoading] = useState(false);

  const handleGalleryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files || []);
    setGalleryImages(files);
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      const files: Record<string, File | File[]> = {};

      if (mainImage) {
        files.main_image = mainImage;
      }

      if (galleryImages.length > 0) {
        files.gallery = galleryImages;
      }

      const product = await db.createDocumentWithFiles(
        'products',
        {
          name,
          price: parseFloat(price),
          status: 'active'
        },
        files
      );

      console.log('Product created:', product);
      alert('Product created successfully!');

      // Reset form
      setName('');
      setPrice('');
      setMainImage(null);
      setGalleryImages([]);
    } catch (error) {
      console.error('Product creation failed:', error);
      alert('Failed to create product');
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <h2 className="text-2xl font-bold">Create Product</h2>

      <input
        type="text"
        placeholder="Product Name"
        value={name}
        onChange={(e) => setName(e.target.value)}
        required
        className="w-full px-3 py-2 border rounded"
      />

      <input
        type="number"
        placeholder="Price"
        value={price}
        onChange={(e) => setPrice(e.target.value)}
        required
        step="0.01"
        className="w-full px-3 py-2 border rounded"
      />

      <div>
        <label className="block text-sm font-medium mb-2">Main Image</label>
        <input
          type="file"
          accept="image/*"
          onChange={(e) => setMainImage(e.target.files?.[0] || null)}
          className="w-full"
        />
      </div>

      <div>
        <label className="block text-sm font-medium mb-2">
          Gallery Images (Multiple)
        </label>
        <input
          type="file"
          accept="image/*"
          multiple
          onChange={handleGalleryChange}
          className="w-full"
        />
        {galleryImages.length > 0 && (
          <p className="text-sm text-gray-600 mt-1">
            {galleryImages.length} image(s) selected
          </p>
        )}
      </div>

      <button
        type="submit"
        disabled={loading}
        className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:bg-gray-400"
      >
        {loading ? 'Creating...' : 'Create Product'}
      </button>
    </form>
  );
}

Best Practices

Always validate file types and sizes before uploading:
const validateImage = (file: File): boolean => {
  const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
  const maxSize = 5 * 1024 * 1024; // 5MB

  if (!validTypes.includes(file.type)) {
    alert('Please upload a valid image file');
    return false;
  }

  if (file.size > maxSize) {
    alert('File size must be less than 5MB');
    return false;
  }

  return true;
};
Provide visual feedback during uploads:
const [loading, setLoading] = useState(false);

const handleUpload = async () => {
  setLoading(true);
  try {
    await db.createDocumentWithFiles(/* ... */);
  } finally {
    setLoading(false);
  }
};
Reduce file sizes before uploading:
import imageCompression from 'browser-image-compression';

const compressImage = async (file: File): Promise<File> => {
  const options = {
    maxSizeMB: 1,
    maxWidthOrHeight: 1920,
    useWebWorker: true
  };
  return await imageCompression(file, options);
};

// Use compressed image
const compressed = await compressImage(originalFile);
await db.createDocumentWithFiles('users', data, { avatar: compressed });
Let users preview images before uploading:
const [preview, setPreview] = useState<string | null>(null);

const handleFileChange = (file: File) => {
  const reader = new FileReader();
  reader.onloadend = () => {
    setPreview(reader.result as string);
  };
  reader.readAsDataURL(file);
};
Provide meaningful error messages:
try {
  await db.createDocumentWithFiles(/* ... */);
} catch (error) {
  if (error.message.includes('Storage limit')) {
    alert('Storage limit exceeded. Please upgrade your plan.');
  } else if (error.message.includes('Invalid file')) {
    alert('Invalid file format. Please use JPG, PNG, or WebP.');
  } else {
    alert('Upload failed. Please try again.');
  }
}

Field Naming Convention

Use descriptive field names for your files:
// User profiles
{ avatar: file, cover_photo: file, profile_picture: file }

// Products
{ main_image: file, thumbnail: file, gallery: [files] }

// Blog posts
{ featured_image: file, inline_images: [files] }

// Documents
{ id_document: file, verification_photo: file }

// Real estate
{ main_photo: file, photos: [files], floor_plan: file }

File Limits

Limit TypeValue
Maximum file size50 MB
Supported formatsAll file types
Files per request10 files
Storage per projectBased on plan

Common Use Cases

User Avatars

await db.registerWithFiles(
  email,
  password,
  { username },
  { avatar: avatarFile }
);

Product Images

await db.createDocumentWithFiles(
  'products',
  { name, price },
  { main_image: mainFile, gallery: [img1, img2, img3] }
);

Document Uploads

await db.createDocumentWithFiles(
  'applications',
  { applicant_name, status },
  { resume: resumePdf, cover_letter: coverPdf }
);

Blog Post Media

await db.createDocumentWithFiles(
  'posts',
  { title, content },
  { featured_image: featuredFile, attachments: [file1, file2] }
);

Next Steps