Local
Filesystem storage for development.
@veloxts/storage provides unified file storage with local filesystem and S3-compatible drivers (AWS S3, Cloudflare R2, MinIO).
pnpm add @veloxts/storage
# For S3/R2 (production)pnpm add @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presignerimport { storagePlugin } from '@veloxts/storage';
app.register(storagePlugin({ driver: 'local', config: { root: './storage', baseUrl: 'http://localhost:3030/files', },}));import { storagePlugin } from '@veloxts/storage';
app.register(storagePlugin({ driver: 's3', config: { bucket: process.env.S3_BUCKET, region: process.env.S3_REGION, },}));import { storagePlugin } from '@veloxts/storage';
app.register(storagePlugin({ driver: 's3', config: { bucket: process.env.R2_BUCKET, region: 'auto', endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`, accessKeyId: process.env.R2_ACCESS_KEY_ID, secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, },}));// Uploadawait ctx.storage.put('avatars/user-123.jpg', buffer, { contentType: 'image/jpeg', visibility: 'public',});
// Downloadconst content = await ctx.storage.get('avatars/user-123.jpg');
// Stream large filesconst stream = await ctx.storage.stream('large-file.zip');
// Get URLconst url = await ctx.storage.url('avatars/user-123.jpg');
// Check existenceconst exists = await ctx.storage.exists('avatars/user-123.jpg');
// Deleteawait ctx.storage.delete('old-file.pdf');Temporary access to private files:
const signedUrl = await ctx.storage.signedUrl('private/document.pdf', { expiresIn: 3600, // 1 hour});// Copyawait ctx.storage.copy('old/path.jpg', 'new/path.jpg');
// Moveawait ctx.storage.move('temp/upload.jpg', 'permanent/file.jpg');
// Metadataconst meta = await ctx.storage.metadata('file.jpg');// { path, size, lastModified, contentType }
// List filesconst { files, hasMore, cursor } = await ctx.storage.list('uploads/', { limit: 100,});Local
Filesystem storage for development.
S3
AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces.
| Feature | Local | S3/R2 |
|---|---|---|
| Shared across instances | No | Yes |
| Persists across deploys | Maybe | Yes |
| CDN integration | Manual | Built-in |
| Scalability | Limited | Unlimited |
| Signed URLs | No | Yes |
| Provider | Best For |
|---|---|
| Cloudflare R2 | Zero egress fees, global edge |
| AWS S3 | Mature ecosystem |
| DigitalOcean Spaces | Simple pricing |
| MinIO | Self-hosted |
S3_BUCKET=my-bucketS3_REGION=us-east-1AWS_ACCESS_KEY_ID=AKIA...AWS_SECRET_ACCESS_KEY=...R2_BUCKET=my-bucketR2_ACCOUNT_ID=your-account-idR2_ACCESS_KEY_ID=...R2_SECRET_ACCESS_KEY=...app.register(storagePlugin({ driver: 's3', config: { bucket: process.env.S3_BUCKET, region: process.env.S3_REGION, accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
// Optional endpoint: process.env.S3_ENDPOINT, // For R2, MinIO forcePathStyle: false, // true for MinIO defaultVisibility: 'private', publicUrl: 'https://cdn.example.com', // CDN URL },}));