remult-media
v0.2.0
Published
Upload and manage media files in Remult applications
Maintainers
Readme
Remult Media
Media upload and management module for Remult applications. Supports local filesystem and S3-compatible cloud storage with path-based access control.
Features
- File uploads to local filesystem or S3-compatible storage
- Framework-specific UI components (Svelte, React, Vue)
- Path-based access control (public, authenticated, owner-only)
- Support for custom permission functions
- Public and private file visibility with signed URLs
- Custom metadata support with JSON storage and querying
- Configurable file key generation (UUID or original filename)
- Date-based and user-based path prefixes
- Uses Flydrive as the storage abstraction layer
Installation
npm install remult-mediaQuick Start
1. Server Setup
// src/server/api.ts
import { remultApi } from 'remult/remult-express'; // or your framework adapter
import { media } from 'remult-media/server';
export const api = remultApi({
modules: [media()]
});Zero config uses default uploads path with public read/insert access.
2. Use Components
Svelte:
<script>
import { UploadInput, UploadBrowser } from 'remult-media/svelte';
import 'remult-media/styles.css';
</script>
<UploadInput />
<UploadBrowser />React:
import { UploadInput, UploadBrowser } from 'remult-media/react';
import 'remult-media/styles.css';
function App() {
return (
<>
<UploadInput />
<UploadBrowser />
</>
);
}Vue:
<script setup>
import { UploadInput, UploadBrowser } from 'remult-media/vue';
import 'remult-media/styles.css';
</script>
<template>
<UploadInput />
<UploadBrowser />
</template>Configuration
Path Configuration
Each path can be configured with permissions, prefixes, storage, and key style:
media({
paths: {
uploads: {
read: true, // Permission: true, false, 'authenticated', 'owner', or function
insert: 'authenticated',
update: 'owner',
delete: 'owner',
prefix: 'YYYY/MM/DD', // Date pattern or function
storage: 'fs', // Storage service name
keyStyle: 'id' // 'id' (default), 'fileName', or custom function
}
}
});Key Style Options
The keyStyle option controls how file keys are generated:
'id'(default): Uses UUID for filenames (e.g.,uploads/2024/01/a1b2c3d4.jpg)'fileName': Keeps original filename, sanitized for storage (e.g.,uploads/2024/01/photo.jpg)- Custom function: Full control over key generation
media({
paths: {
documents: {
keyStyle: 'fileName', // Keep original filenames
read: true,
insert: true
},
avatars: {
keyStyle: 'id', // Use UUIDs (default)
read: true,
insert: 'authenticated'
},
custom: {
// Custom key generation
keyStyle: ({ fileName, extension, user }) => {
return `${user?.id || 'anon'}-${Date.now()}.${extension}`;
},
read: true,
insert: 'authenticated'
}
}
});Multiple Paths
media({
paths: {
public: {
read: true,
insert: true
},
'user-files': {
read: 'owner',
insert: 'authenticated',
delete: 'owner',
prefix: ({ user }) => user?.id || 'anonymous'
}
}
});Storage Configuration
import { media, fsStorage, s3Storage } from 'remult-media/server';
media({
paths: {
public: { read: true, insert: true, storage: 'fs' },
avatars: { read: true, insert: 'authenticated', storage: 's3' }
},
storage: {
default: 'fs',
services: {
fs: fsStorage({
location: './static/media',
urlPrefix: '/media'
}),
s3: s3Storage({
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
},
bucket: 'my-app-avatars',
visibility: 'public'
})
}
}
});Custom Metadata
Files can have custom metadata attached as JSON, which can be queried using Remult's filter syntax.
Uploading with Metadata
Svelte:
<UploadInput
path="uploads"
meta={{ category: 'avatar', userId: '123' }}
/>React:
<UploadInput
path="uploads"
meta={{ category: 'avatar', userId: '123' }}
/>Vue:
<UploadInput
path="uploads"
:meta="{ category: 'avatar', userId: '123' }"
/>Filtering by Metadata
Use the where prop to filter files by metadata:
Svelte:
<UploadBrowser
path="uploads"
where={{ meta: { category: 'avatar' } }}
/>
<MediaPicker
path="uploads"
where={{ meta: { category: 'avatar' } }}
meta={{ category: 'avatar' }}
/>React:
<UploadBrowser
path="uploads"
where={{ meta: { category: 'avatar' } }}
/>
// Or use the hook directly
const { media, upload } = useMedia({
path: 'uploads',
where: { meta: { category: 'avatar' } },
meta: { category: 'avatar' }
});Vue:
<UploadBrowser
path="uploads"
:where="{ meta: { category: 'avatar' } }"
/>Querying Metadata with Remult
import { repo } from 'remult';
import { MediaWithUrl } from 'remult-media';
// Find all avatars
const avatars = await repo(MediaWithUrl).find({
where: { meta: { category: 'avatar' } }
});
// Find files by user
const userFiles = await repo(MediaWithUrl).find({
where: { meta: { userId: '123' } }
});Exports
The package provides subpath exports for each framework:
| Import | Description |
|--------|-------------|
| remult-media | Core entities and types (Media, MediaWithUrl) |
| remult-media/server | Server module (media, fsStorage, s3Storage) |
| remult-media/svelte | Svelte 5 components |
| remult-media/react | React components and hooks |
| remult-media/vue | Vue 3 components |
| remult-media/styles.css | Component styles |
Components
All framework packages export the same components:
| Component | Description |
|-----------|-------------|
| UploadInput | File input with drag-and-drop support. Props: path, display, meta, onUploaded |
| UploadBrowser | Gallery view of uploaded files. Props: path, display, where |
| MediaUpload | Combined upload input and browser |
| MediaPicker | Modal picker for selecting uploaded media. Props: path, where, meta, value, onchange |
| FilePreview | Single file preview component |
React Hook
import { useMedia } from 'remult-media/react';
function MyComponent() {
const { media, loading, upload, deleteMedia, refresh } = useMedia({
path: 'uploads',
where: { meta: { category: 'photos' } },
meta: { category: 'photos' }
});
const handleUpload = async (file: File) => {
// Uses default meta from options
await upload(file);
// Or override with custom meta
await upload(file, { category: 'special' });
};
return (
<div>
{media.map(m => (
<div key={m.id}>
<img src={m.url} alt={m.name} />
<button onClick={() => deleteMedia(m.id)}>Delete</button>
</div>
))}
</div>
);
}Development
# Install dependencies
pnpm install
# Build the library
pnpm build
# Run Svelte demo
pnpm dev:svelte
# Run React demo
pnpm dev:react
# Run Vue demo
pnpm dev:vue
# Type check
pnpm checkRequirements
- Remult 3.3.1+
- Svelte 5.0.0+ (for Svelte components)
- React 18.0.0+ (for React components)
- Vue 3.0.0+ (for Vue components)
License
MIT
