Storage

Receive uploads from your users and store them on your server's disk, with no cloud provider required. Manage references to uploaded files and associate them with records in your business domain.

Level: Medium
19
583
1
61 downloads

Install deps

Install the following dependencies to get started. You can find their documentation on HexDocs.

Adding uploaders in JS

The uploaders module is imported and passed to the LiveSocket configuration. This lets LiveView hand each upload to the uploader named in the Cloud configuration.

Defining uploaders

A new file exports the LiveUploader as the default uploader for the application.

Live uploader

The uploader sends each file with a PUT to the signed URL that MyApp.Cloud.live_upload/1 returns. The script manages the file upload lifecycle, providing progress updates through the entry.progress() callback and handling errors appropriately.

Cloud configuration

Settings shared by every environment: the Volume adapter, a path prefix inside the bucket, the endpoint whose secret key signs upload and download URLs, and the uploader name that matches the JavaScript uploader.

In development, files are stored under tmp/buckets_volume .

Tests use their own directory, so test uploads stay out of development data.

In production, files are stored under /data/storage , where you should mount a persistent volume so files survive deploys. This is set at compile time, not in runtime.exs , because the router builds the Volume routes from the bucket path when it compiles.

The base URL for signed URLs comes from PHX_HOST .

Cloud

This is your cloud module, which you'll use to manage objects. Think of this module similarly to your MyApp.Repo module, but for files. It uses the Buckets library with the configuration from your app's config.

This feature stores files with the Volume adapter in every environment, so it has no reliance on a cloud provider. The rest of the app only talks to MyApp.Cloud , so moving to a cloud provider later is a configuration change. Buckets supports these storage strategies:

Local filesystem
Buckets.Adapters.Volume
Google Cloud Storage
Buckets.Adapters.GCS
Cloudflare R2, DigitalOcean Spaces, and Tigris
Buckets.Adapters.S3 with the provider option

The Volume adapter suits a single server with a persistent disk. Your app receives every upload and serves every download, and its signed URLs don't expire. Move to a cloud adapter when you run more than one server, want uploads to go straight from the browser to the bucket, or need expiring URLs.

For each adapter's configuration, credentials, and setup like CORS rules for direct uploads, see the Buckets documentation on GitHub .

Storage context

The Storage context encapsulates all interactions with storage objects. It provides functions for:

The soft-delete pattern allows for a grace period before permanent deletion, useful for implementing trash/recovery features.

Object schema

The Object schema stores metadata about uploaded files. Fields include:

The module provides query helpers for filtering present (non-deleted) and deleted objects, with optional duration thresholds for cleanup operations.

The insert_assocs_multi/3 function allows atomically inserting related records (like the uploader relationship) as part of the same transaction.

Helper functions include image?/1 for checking content type, signed_url/1 for generating access URLs, and remote_object/1 for reconstructing the Buckets object from database fields.

Object uploaded by user schema

This join table links objects with the users who uploaded them. It uses a composite key of object_id and uploaded_by_user_id .

The uploaded_by_user_query/2 function joins objects with this table to filter by uploader, ensuring users can only access their own uploads.

Storage controller

The StorageController handles secure file downloads. The download/2 action verifies a signature parameter before allowing access to the file.

The signature is generated using deterministic_hash/1 , which creates an HMAC using the application's secret key base. This prevents unauthorized access to files by ID alone.

The download_path/1 function generates signed download URLs for use in templates.

Storage LiveView

The StorageLive module provides an interface for uploading and managing files. It configures uploads with:

The presign_upload/2 function gets a signed upload URL from the Cloud module.

The handle_progress/3 callback consumes completed uploads, creating object records and updating the UI stream.

Users can view their uploads in a table showing filename, content type, preview (for images), size, and timestamp. Actions include delete (soft-delete) and download.

Storage components

Reusable components for the storage interface:

These components can be reused by other features that need file upload functionality.

Router updates

Routes for file storage are added:

The buckets_volume macro mounts GET and PUT routes at /__buckets__ for downloading and uploading files stored by the Volume adapter. They're mounted in every environment, since production stores files on disk too, and both reject requests without a valid signature. If you switch to a cloud adapter, remove them.

Migration

Creates tables for storing file metadata:

Both tables use UUID primary keys and appropriate foreign key constraints with cascade deletes.