npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@simo777/fastify-storage

v1.0.4

Published

Scalable Fastify storage plugin for Local and R2/S3 storage with Prisma metadata tracking and media processing.

Readme

Fastify Storage Plugin

A scalable storage plugin for Fastify that supports local file storage and Cloudflare R2 (or any S3-compatible provider).

Features

  • Deduplication: Uses SHA-256 hashing to prevent duplicate file uploads.
  • Media Processing: Automatically extracts dimensions (width/height) for images/videos and duration for audio/video.
  • Multi-provider Support: Switch between local and r2 (S3-compatible) via typed options.
  • Prisma Integration: Automatically tracks file metadata in your database.
  • Multipart Support: Handles file uploads out of the box.
  • Local Serving: Automatically serves local uploads via @fastify/static.
  • SDK Included: Type-safe SDK for interacting with the storage API.
  • Custom Paths: Organize files into folders (e.g., /avatars/user-1.jpg) across all providers.

Database Setup

Add the following model to your prisma/schema.prisma file:

model StoredFile {
  id           String   @id @default(cuid())
  key          String   @unique
  bucket       String
  provider     String   // "local", "r2", etc.
  originalName String
  mimeType     String
  size         Int
  url          String
  type         String   // image | audio | video | file
  hash         String   @unique // SHA-256 dedup key 
  width        Int?
  height       Int?
  duration     Int?
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
}

Then run:

npx prisma migrate dev --name add_stored_file

Configuration

Add these variables to your .env file:

# Storage Provider: 'local' or 'r2'
STORAGE_PROVIDER=local

# Local Storage Configuration
LOCAL_STORAGE_PATH=uploads
LOCAL_STORAGE_PUBLIC_URL=http://localhost:3000/uploads

# Cloudflare R2 / S3 Configuration
R2_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID=your_access_key
R2_SECRET_ACCESS_KEY=your_secret_key
R2_BUCKET_NAME=your_bucket_name
R2_PUBLIC_URL=https://pub-<your-id>.r2.dev

Usage

Server-side (Fastify)

Register the plugin with your desired configuration. The plugin automatically handles @fastify/multipart and @fastify/static (for local storage).

import fastifyStorage from '@simo777/fastify-storage';

await app.register(fastifyStorage, {
  provider: 'local', // or 'r2'
  routePrefix: '/storage', // Optional: defaults to /storage
  defaultPath: 'uploads', // Optional: global default folder
  
  // Optional: customize multipart limits
  multipart: {
    limits: { fileSize: 20 * 1024 * 1024 } // 20MB
  },
  
  // Optional: customize static file serving (for local provider)
  static: {
    cacheControl: true,
    maxAge: 3600000
  },

  local: {
    storagePath: 'uploads',
    publicUrl: 'http://localhost:3000/uploads'
  },
  // r2: { ... }
});

The plugin decorates the Fastify instance with storage. You can use it in your services or controllers.

Uploading a File

// Inside a route or service
const buffer = await data.toBuffer();
const key = 'avatar.jpg';

await fastify.storage.upload(buffer, {
  key,
  contentType: 'image/jpeg',
  path: 'profiles/users' // Optional: results in profiles/users/avatar.jpg
});

Deleting a File

await fastify.storage.delete('file-key.pdf');

Checking existence

const exists = await fastify.storage.exists('file-key.pdf');

API Endpoints

The plugin provides a module with the following endpoints (prefixed by your API_PREFIX):

  • POST /storage/upload: Upload a file (Multipart form-data). Supports optional path field.
  • POST /storage/upload-url: Upload a file from a remote URL. Body: { "url": "...", "path": "..." }.
  • GET /storage: List all files in the database.
  • GET /storage/:id: Get metadata for a specific file.
  • PUT /storage/:id: Replace an existing file.
  • DELETE /storage/:id: Delete a file from storage and database.
  • GET /storage/signed-url?key=...: Get a signed upload URL (R2 only).

Using the SDK

import { AppSDK } from './sdk';

const sdk = new AppSDK({
  baseUrl: 'http://localhost:3000/api/v1'
});

// Upload a file from the browser to a specific folder
const file = fileInput.files[0];
const storedFile = await sdk.storage.upload(file, 'profile.jpg', 'avatars');

// Upload from a URL
const urlFile = await sdk.storage.uploadFromUrl('https://example.com/image.png', 'external');

console.log(storedFile.url); // e.g., .../avatars/profile.jpg

// List files
const files = await sdk.storage.list();

Testing with Postman

1. Upload a File

  • Method: POST
  • URL: http://localhost:3000/api/v1/storage/upload
  • Body: Select form-data
    • Key: file, Value: [Select File], Type: File
    • Key: path, Value: my-folder, Type: Text (Optional)

2. List All Files

  • Method: GET
  • URL: http://localhost:3000/api/v1/storage

3. Replace a File

  • Method: PUT
  • URL: http://localhost:3000/api/v1/storage/:id (Replace :id with a file ID from the list)
  • Body: form-data
    • Key: file
    • Value: Select a new file

4. Delete a File

  • Method: DELETE
  • URL: http://localhost:3000/api/v1/storage/:id

Recommendations for Testing

  1. Local vs Production:
    • Use STORAGE_PROVIDER=local for development and CI to avoid costs and network latency.
    • Use STORAGE_PROVIDER=r2 in a staging environment that mirrors production.
  2. MIME Type Validation:
    • The current implementation accepts all file types. For production, consider adding a hook or validation logic in src/modules/storage/index.ts to restrict allowed MIME types (e.g., image/jpeg, application/pdf).
  3. File Size Limits:
    • Configure @fastify/multipart limits in src/app.ts to prevent large file uploads from crashing your server or filling up disk space:
      await app.register(import('@fastify/multipart'), {
        limits: { fileSize: 10 * 1024 * 1024 } // 10MB
      });
  4. Error Handling:
    • Test what happens when the storage provider is unreachable (e.g., wrong R2 credentials). The plugin will currently throw a 500 error, which is caught by the global error handler.
  5. Database Sync:
    • Verify that if a file upload fails, no entry is created in the database.
    • Verify that if a database deletion fails, the file is not deleted from the storage provider (or handle the rollback logic).

Adding New Providers

To add a new provider (e.g., Azure Blob Storage):

  1. Implement the StorageProvider interface in src/lib/storage/.
  2. Update the factory in src/lib/storage/index.ts to include your new provider.