Examples
Comprehensive examples showing real-world usage patterns for @minimajs/disk.
Table of Contents
- Basic Operations
- Image Processing
- PDF Generation
- File Uploads
- Backup & Sync
- Testing Strategies
- Production Patterns
Basic Operations
Upload and Download
typescript
import { createDisk } from "@minimajs/disk";
import { createFsDriver } from "@minimajs/disk/adapters/fs";
const disk = createDisk({
driver: createFsDriver({ root: "./storage" }),
});
// Upload text file
const textFile = await disk.put("documents/readme.txt", "Hello, World!", { type: "text/plain" });
console.log(textFile.href); // file:///storage/documents/readme.txt
// Download as text
const file = await disk.get("documents/readme.txt");
const text = await file.text();
console.log(text); // Hello, World!
// Download as buffer
const buffer = await file.arrayBuffer();
// Download as stream
const stream = file.stream();Check Existence
typescript
// Check if file exists
const exists = await disk.exists("documents/readme.txt");
console.log(exists); // true
// Get file or null
const file = await disk.get("documents/missing.txt");
if (!file) {
console.log("File not found");
}
// Try/catch approach
try {
const file = await disk.get("documents/missing.txt");
} catch (error) {
if (error instanceof DiskFileNotFoundError) {
console.log("File not found");
}
}Copy and Move
typescript
// Copy file
await disk.copy("documents/readme.txt", "backups/readme-backup.txt");
// Move file
await disk.move("documents/temp.txt", "documents/permanent.txt");
// Copy with overwrite
await disk.copy("documents/source.txt", "documents/destination.txt", { overwrite: true });List Files
typescript
// List all files
for await (const file of disk.list()) {
console.log(file.href, file.size, file.lastModified);
}
// List with prefix
for await (const file of disk.list("documents/2024/")) {
console.log(file.name);
}
// List with limit
let count = 0;
for await (const file of disk.list("documents/")) {
console.log(file.href);
if (++count >= 10) break; // Only list 10 files
}Image Processing
Avatar Upload with Thumbnails
typescript
import { createDisk } from "@minimajs/disk";
import { createS3Driver } from "@minimajs/aws-s3";
import sharp from "sharp";
const disk = createDisk({
driver: createS3Driver({
bucket: "user-avatars",
region: "us-east-1",
credentials: {
/* ... */
},
acl: "public-read",
}),
});
async function uploadAvatar(userId: string, imageBuffer: Buffer) {
// Original image
const original = await disk.put(`avatars/${userId}/original.jpg`, imageBuffer, { type: "image/jpeg" });
// Large thumbnail (800x800)
const largeThumbnail = await sharp(imageBuffer).resize(800, 800, { fit: "cover" }).jpeg({ quality: 90 }).toBuffer();
const large = await disk.put(`avatars/${userId}/large.jpg`, largeThumbnail, { type: "image/jpeg" });
// Medium thumbnail (400x400)
const mediumThumbnail = await sharp(imageBuffer).resize(400, 400, { fit: "cover" }).jpeg({ quality: 85 }).toBuffer();
const medium = await disk.put(`avatars/${userId}/medium.jpg`, mediumThumbnail, { type: "image/jpeg" });
// Small thumbnail (100x100)
const smallThumbnail = await sharp(imageBuffer).resize(100, 100, { fit: "cover" }).jpeg({ quality: 80 }).toBuffer();
const small = await disk.put(`avatars/${userId}/small.jpg`, smallThumbnail, { type: "image/jpeg" });
return {
original: await disk.url(original.href),
large: await disk.url(large.href),
medium: await disk.url(medium.href),
small: await disk.url(small.href),
};
}
// Usage
const urls = await uploadAvatar("user-123", avatarBuffer);
console.log(urls.small); // https://...Image Watermarking
typescript
async function addWatermark(inputPath: string, outputPath: string) {
// Get original image
const original = await disk.get(inputPath);
const imageBuffer = Buffer.from(await original.arrayBuffer());
// Add watermark
const watermarked = await sharp(imageBuffer)
.composite([
{
input: await sharp({
text: {
text: "© 2024 MyApp",
font: "Arial",
rgba: true,
},
})
.png()
.toBuffer(),
gravity: "southeast",
},
])
.toBuffer();
// Save watermarked image
await disk.put(outputPath, watermarked, {
type: original.type,
});
}
await addWatermark("uploads/photo.jpg", "processed/photo-watermarked.jpg");PDF Generation
Invoice Generator
typescript
import { createDisk } from "@minimajs/disk";
import { createS3Driver } from "@minimajs/aws-s3";
import PDFDocument from "pdfkit";
const disk = createDisk({
driver: createS3Driver({
bucket: "invoices",
region: "us-east-1",
credentials: {
/* ... */
},
}),
});
interface Invoice {
id: string;
customerName: string;
items: Array<{ name: string; price: number; quantity: number }>;
total: number;
}
async function generateInvoice(invoice: Invoice): Promise<string> {
// Create PDF in memory
const chunks: Buffer[] = [];
const doc = new PDFDocument();
doc.on("data", (chunk) => chunks.push(chunk));
// PDF content
doc.fontSize(20).text(`Invoice #${invoice.id}`, 100, 100);
doc.fontSize(12).text(`Customer: ${invoice.customerName}`, 100, 150);
let y = 200;
invoice.items.forEach((item) => {
doc.text(`${item.name} x${item.quantity} - $${item.price * item.quantity}`, 100, y);
y += 20;
});
doc.fontSize(16).text(`Total: $${invoice.total}`, 100, y + 20);
doc.end();
// Wait for PDF to finish
const pdfBuffer = await new Promise<Buffer>((resolve) => {
doc.on("end", () => resolve(Buffer.concat(chunks)));
});
// Upload to S3
const file = await disk.put(`invoices/${invoice.id}.pdf`, pdfBuffer, {
type: "application/pdf",
metadata: {
invoiceId: invoice.id,
customerName: invoice.customerName,
generatedAt: new Date().toISOString(),
},
});
// Generate signed URL (expires in 7 days)
return await disk.url(file.href, { expiresIn: 604800 });
}
// Usage
const invoiceUrl = await generateInvoice({
id: "INV-2024-001",
customerName: "John Doe",
items: [
{ name: "Widget A", price: 10, quantity: 2 },
{ name: "Widget B", price: 15, quantity: 1 },
],
total: 35,
});
console.log(invoiceUrl); // https://...File Uploads
Multipart Upload Handler
typescript
import { createDisk, storeAs } from "@minimajs/disk";
import { createS3Driver } from "@minimajs/aws-s3";
import { Application } from "@minimajs/server";
import { multipart } from "@minimajs/multipart";
const app = new Application();
// storeAs("uuid-original") renames files to "<uuid>-originalname.ext" automatically
const disk = createDisk(
{
driver: createS3Driver({
bucket: "uploads",
region: "us-east-1",
credentials: {
/* ... */
},
}),
},
storeAs("uuid-original")
);
app.use(multipart());
app.post("/upload", async (ctx) => {
const files = ctx.get("files");
if (!files || files.length === 0) {
return ctx.json({ error: "No files uploaded" }, 400);
}
const uploadedFiles = await Promise.all(
files.map(async (file) => {
// Put the File directly — storeAs generates a unique name automatically
// file.metadata.originalName holds the original filename
const uploaded = await disk.put(file, {
metadata: {
uploadedBy: ctx.get("userId") ?? "anonymous",
uploadedAt: new Date().toISOString(),
},
});
// Generate public URL
const url = await disk.url(uploaded.href);
return {
name: uploaded.metadata.originalName ?? uploaded.name,
size: uploaded.size,
type: uploaded.type,
url,
};
})
);
return ctx.json({ files: uploadedFiles });
});File Size Validation
typescript
import { DiskWriteError } from "@minimajs/disk";
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
async function uploadWithValidation(path: string, file: File): Promise<string> {
// Check file size
if (file.size > MAX_FILE_SIZE) {
throw new Error(`File too large: ${file.size} bytes`);
}
// Check file type
const allowedTypes = ["image/jpeg", "image/png", "image/webp"];
if (!allowedTypes.includes(file.type)) {
throw new Error(`Invalid file type: ${file.type}`);
}
try {
// Upload
const uploaded = await disk.put(path, file, {
type: file.type,
metadata: {
originalName: file.name,
uploadedAt: new Date().toISOString(),
},
});
return await disk.url(uploaded.href);
} catch (error) {
if (error instanceof DiskWriteError) {
throw new Error(`Upload failed: ${error.message}`);
}
throw error;
}
}Backup & Sync
Cross-Storage Backup
typescript
import { createProtocolDisk } from "@minimajs/disk";
import { createS3Driver } from "@minimajs/aws-s3";
// Primary storage (S3)
const primary = createS3Driver({
bucket: "prod-data",
region: "us-east-1",
credentials: {
/* ... */
},
});
// Backup storage (Different region)
const backup = createS3Driver({
bucket: "backup-data",
region: "us-west-2",
credentials: {
/* ... */
},
});
const disk = createProtocolDisk({
protocols: {
"s3://prod-data/": primary,
"s3://backup-data/": backup,
},
});
async function backupFile(path: string): Promise<void> {
const primaryPath = `s3://prod-data/${path}`;
const backupPath = `s3://backup-data/${path}`;
// Check if file exists in primary
if (!(await disk.exists(primaryPath))) {
throw new Error(`File not found: ${path}`);
}
// Copy to backup (streams data between regions)
await disk.copy(primaryPath, backupPath, { overwrite: true });
console.log(`Backed up: ${path}`);
}
// Backup multiple files
const filesToBackup = ["documents/important.pdf", "data/export.csv"];
await Promise.all(filesToBackup.map(backupFile));Scheduled Backup
typescript
import { CronJob } from "cron";
// Backup job (runs daily at 2 AM)
const backupJob = new CronJob("0 2 * * *", async () => {
console.log("Starting daily backup...");
const files = [];
for await (const file of disk.list("s3://prod-data/")) {
files.push(file);
}
console.log(`Found ${files.length} files to backup`);
let backed = 0;
for (const file of files) {
try {
const path = file.href.replace("s3://prod-data/", "");
await backupFile(path);
backed++;
} catch (error) {
console.error(`Failed to backup ${file.href}:`, error);
}
}
console.log(`Backup complete: ${backed}/${files.length} files`);
});
backupJob.start();Testing Strategies
Memory Driver for Tests
typescript
import { createDisk } from "@minimajs/disk";
import { createMemoryDriver } from "@minimajs/disk/adapters/memory";
import { describe, it, expect, beforeEach } from "@jest/globals";
describe("FileService", () => {
let disk: ReturnType<typeof createDisk>;
beforeEach(() => {
// Fresh memory storage for each test
disk = createDisk({
driver: createMemoryDriver(),
});
});
it("should upload and download files", async () => {
// Upload
await disk.put("test.txt", "Hello, World!");
// Download
const file = await disk.get("test.txt");
expect(await file.text()).toBe("Hello, World!");
});
it("should handle missing files", async () => {
const file = await disk.get("missing.txt");
expect(file).toBeNull();
});
it("should list files", async () => {
await disk.put("file1.txt", "Content 1");
await disk.put("file2.txt", "Content 2");
await disk.put("file3.txt", "Content 3");
const files = [];
for await (const file of disk.list()) {
files.push(file);
}
expect(files).toHaveLength(3);
});
});Mocking External Storage
typescript
import { describe, it, expect, vi } from "vitest";
describe("ImageProcessor", () => {
it("should process and upload images", async () => {
// Mock disk operations
const mockDisk = {
put: vi.fn().mockResolvedValue({
href: "s3://bucket/image.jpg",
type: "image/jpeg",
size: 1024,
}),
url: vi.fn().mockResolvedValue("https://cdn.example.com/image.jpg"),
};
const processor = new ImageProcessor(mockDisk);
const result = await processor.processAvatar("user-123", imageBuffer);
expect(mockDisk.put).toHaveBeenCalledWith("avatars/user-123/original.jpg", expect.any(Buffer), { type: "image/jpeg" });
expect(result).toEqual({
url: "https://cdn.example.com/image.jpg",
});
});
});Production Patterns
Environment-Based Storage
typescript
import { createProtocolDisk } from "@minimajs/disk";
import { createS3Driver } from "@minimajs/aws-s3";
import { createFsDriver } from "@minimajs/disk/adapters/fs";
function createStorageDisk() {
if (process.env.NODE_ENV === "production") {
// Production: Use S3
return createDisk({
driver: createS3Driver({
bucket: process.env.S3_BUCKET!,
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
}),
});
} else {
// Development: Use filesystem
return createDisk({
driver: createFsDriver({
root: "./storage",
}),
});
}
}
export const disk = createStorageDisk();CDN Integration
typescript
const disk = createDisk({
driver: createS3Driver({
bucket: "my-bucket",
region: "us-east-1",
credentials: {
/* ... */
},
acl: "public-read",
}),
});
async function uploadWithCDN(path: string, data: Buffer | ReadableStream): Promise<string> {
// Upload to S3
const file = await disk.put(path, data);
// Return CloudFront URL instead of S3 URL
const cdnDomain = process.env.CDN_DOMAIN!; // d1234567890.cloudfront.net
const cdnUrl = `https://${cdnDomain}/${path}`;
return cdnUrl;
}Error Recovery
typescript
import { DiskWriteError, DiskReadError, DiskFileNotFoundError } from "@minimajs/disk";
async function uploadWithRetry(path: string, data: Buffer, maxRetries = 3): Promise<string> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const file = await disk.put(path, data);
return await disk.url(file.href);
} catch (error) {
lastError = error as Error;
if (error instanceof DiskWriteError) {
console.error(`Upload attempt ${attempt}/${maxRetries} failed:`, error.message);
if (attempt < maxRetries) {
// Exponential backoff
await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt - 1)));
continue;
}
}
// Non-recoverable error
throw error;
}
}
throw new Error(`Upload failed after ${maxRetries} attempts: ${lastError?.message}`);
}Cleanup Old Files
typescript
async function cleanupOldFiles(olderThanDays: number): Promise<number> {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - olderThanDays);
let deletedCount = 0;
for await (const file of disk.list("temp/")) {
if (file.lastModified < cutoff) {
try {
await disk.delete(file.href);
deletedCount++;
console.log(`Deleted: ${file.href}`);
} catch (error) {
console.error(`Failed to delete ${file.href}:`, error);
}
}
}
return deletedCount;
}
// Run cleanup daily
import { CronJob } from "cron";
new CronJob("0 3 * * *", async () => {
const deleted = await cleanupOldFiles(30); // Delete files older than 30 days
console.log(`Cleanup complete: ${deleted} files deleted`);
}).start();