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

@xenterprises/fastify-xstorage

v1.2.1

Published

Fastify plugin for S3-compatible storage with simple, intuitive API. Supports AWS S3, Cloudflare R2, Digital Ocean Spaces and any S3-compatible provider.

Readme

@xenterprises/fastify-xstorage

Fastify v5 plugin for S3-compatible file storage with a simple, intuitive API.

Upload, download, delete, list, copy, and move files on AWS S3, Cloudflare R2, Digital Ocean Spaces, or any S3-compatible provider. Default ACL is private — use signed URLs for secure access.

Install

npm install @xenterprises/fastify-xstorage

For HTTP file uploads, also install:

npm install @fastify/multipart@9

Quick Start

import Fastify from "fastify";
import xStorage from "@xenterprises/fastify-xstorage";

const fastify = Fastify({ logger: true });

await fastify.register(xStorage, {
  endpoint: "https://nyc3.digitaloceanspaces.com",
  region: "us-east-1",
  accessKeyId: process.env.STORAGE_ACCESS_KEY_ID,
  secretAccessKey: process.env.STORAGE_SECRET_ACCESS_KEY,
  bucket: "my-bucket",
  publicUrl: "https://my-bucket.nyc3.digitaloceanspaces.com",
});

// Upload a file
const result = await fastify.xStorage.upload(buffer, "report.pdf", { folder: "docs" });
// → { key: "docs/report-a1b2c3d4.pdf", url: "https://…/docs/report-a1b2c3d4.pdf", size: 123456, contentType: "application/pdf", bucket: "my-bucket" }

// Get a signed download URL (1 hour)
const url = await fastify.xStorage.getSignedUrl(result.key, 3600);

await fastify.listen({ port: 3000 });

Plugin Options

| Option | Type | Default | Required | Description | |--------|------|---------|----------|-------------| | endpoint | string | — | No* | S3 endpoint URL. Required for non-AWS providers. | | region | string | "us-east-1" | No | AWS region or provider region. | | accessKeyId | string | — | Yes | S3 access key ID. | | secretAccessKey | string | — | Yes | S3 secret access key. | | bucket | string | — | Yes | Bucket name. | | publicUrl | string | — | Yes | Public URL base for the bucket. | | forcePathStyle | boolean | true | No | Use path-style URLs (required for DO Spaces, R2). | | acl | string | "private" | No | Default ACL for uploads ("private" or "public-read"). |

*For AWS S3, omit endpoint and set region instead.

Provider Configuration

Digital Ocean Spaces

await fastify.register(xStorage, {
  endpoint: "https://nyc3.digitaloceanspaces.com",
  region: "nyc3",
  accessKeyId: process.env.DO_SPACES_KEY,
  secretAccessKey: process.env.DO_SPACES_SECRET,
  bucket: "my-bucket",
  publicUrl: "https://my-bucket.nyc3.digitaloceanspaces.com",
  forcePathStyle: true,
});

AWS S3

await fastify.register(xStorage, {
  region: "us-east-1",
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  bucket: "my-bucket",
  publicUrl: "https://my-bucket.s3.us-east-1.amazonaws.com",
  forcePathStyle: false,
});

Cloudflare R2

await fastify.register(xStorage, {
  endpoint: "https://ACCOUNT_ID.r2.cloudflarestorage.com",
  region: "auto",
  accessKeyId: process.env.R2_ACCESS_KEY_ID,
  secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
  bucket: "my-bucket",
  publicUrl: "https://cdn.example.com",
  forcePathStyle: true,
});

Decorated Properties

All methods are available on fastify.xStorage.

| Method | Signature | Returns | Description | |--------|-----------|---------|-------------| | upload | (file, filename, options?) | Promise<UploadResult> | Upload a single file. | | uploadMultiple | (files, options?) | Promise<UploadResult[]> | Upload multiple files concurrently. | | download | (key) | Promise<Buffer> | Download a file as a buffer. | | delete | (key) | Promise<boolean> | Delete a file. | | deleteMultiple | (keys) | Promise<{deleted, errors}> | Batch-delete using S3 DeleteObjects. | | copy | (sourceKey, destKey, options?) | Promise<{key, url}> | Copy a file. | | move | (sourceKey, destKey, options?) | Promise<{key, url}> | Move a file (copy + delete source). | | exists | (key) | Promise<boolean> | Check if a file exists. | | getMetadata | (key) | Promise<Metadata> | Get file metadata (size, type, etag, etc.). | | list | (prefix?, maxKeys?) | Promise<FileInfo[]> | List files (single page, default 1000). | | listAll | (prefix?) | Promise<FileInfo[]> | List all files with automatic pagination. | | getSignedUrl | (key, expiresIn?) | Promise<string> | Pre-signed download URL (default: 1 hour). | | getSignedUploadUrl | (key, options?) | Promise<{uploadUrl, key, publicUrl}> | Pre-signed upload URL for direct client uploads. | | getPublicUrl | (key) | string | Public URL (only works with public-read ACL). |

Upload Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | folder | string | "" | Folder path prefix (e.g. "docs/2024"). | | key | string | null | Custom storage key (overrides folder + filename). | | contentType | string | auto-detected | MIME type. | | metadata | object | {} | Custom S3 metadata. | | useRandomName | boolean | true | Append random hex to filename. | | acl | string | plugin default | Per-file ACL override. |

getSignedUploadUrl Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | contentType | string | "application/octet-stream" | Expected MIME type. | | expiresIn | number | 3600 | URL lifetime in seconds. | | metadata | object | {} | Custom S3 metadata. |

Environment Variables

| Variable | Required | Description | |----------|----------|-------------| | STORAGE_ENDPOINT | No* | S3 endpoint URL | | STORAGE_REGION | No | AWS region (default: us-east-1) | | STORAGE_ACCESS_KEY_ID | Yes | S3 access key | | STORAGE_SECRET_ACCESS_KEY | Yes | S3 secret key | | STORAGE_BUCKET | Yes | Bucket name | | STORAGE_PUBLIC_URL | Yes | Public URL base |

*Required for non-AWS providers (DO Spaces, R2, etc.).

Error Reference

All errors thrown by the plugin are prefixed with [xStorage].

| Error | When | |-------|------| | [xStorage] accessKeyId is required and must be a string | Missing or invalid accessKeyId at startup. | | [xStorage] secretAccessKey is required and must be a string | Missing or invalid secretAccessKey at startup. | | [xStorage] bucket is required and must be a string | Missing or invalid bucket at startup. | | [xStorage] publicUrl is required and must be a string | Missing or invalid publicUrl at startup. | | [xStorage] endpoint must be a string when provided | Non-string endpoint at startup. | | [xStorage] forcePathStyle must be a boolean | Non-boolean forcePathStyle at startup. | | [xStorage] file is required for upload | null/undefined file passed to upload. | | [xStorage] filename is required and must be a string | Missing/invalid filename in upload. | | [xStorage] key is required and must be a string | Missing key in download/delete/exists/metadata/URL methods. | | [xStorage] keys must be a non-empty array of strings | Invalid input to deleteMultiple. | | [xStorage] files must be a non-empty array | Invalid input to uploadMultiple. | | [xStorage] sourceKey is required and must be a string | Missing source key in copy/move. | | [xStorage] destinationKey is required and must be a string | Missing destination key in copy/move. | | [xStorage] expiresIn must be a positive number (seconds) | Invalid expiration for getSignedUrl. | | [xStorage] Failed to upload file "…": … | S3 upload error. | | [xStorage] Failed to download file "…": … | S3 download error. | | [xStorage] Failed to delete file "…": … | S3 delete error. | | [xStorage] Failed to delete multiple files: … | S3 batch delete error. | | [xStorage] Failed to copy "…" to "…": … | S3 copy error. | | [xStorage] Failed to check existence of "…": … | S3 head error (not a 404). | | [xStorage] Failed to get metadata for "…": … | S3 head error. | | [xStorage] Failed to list files with prefix "…": … | S3 list error. | | [xStorage] Failed to generate signed URL for "…": … | Presigner error. | | [xStorage] Failed to generate signed upload URL for "…": … | Presigner error. |

Helper Utilities

Exported separately: import { helpers } from "@xenterprises/fastify-xstorage" or import { helpers } from "@xenterprises/fastify-xstorage/helpers".

| Helper | Signature | Returns | Description | |--------|-----------|---------|-------------| | formatFileSize | (bytes) | string | Human-readable file size (e.g. "1.18 MB"). | | isImage | (filename) | boolean | Check image extension (jpg, png, webp, avif, svg, gif, bmp). | | isPdf | (filename) | boolean | Check PDF extension. | | isVideo | (filename) | boolean | Check video extension (mp4, mov, webm, etc.). | | isValidFileType | (filename, allowedTypes) | boolean | Check against allowed extensions list. | | sanitizeFilename | (filename) | string | Remove unsafe characters, lowercase. | | getFileExtension | (filename) | string | Extract lowercase extension. | | getFilenameWithoutExtension | (filename) | string | Filename without extension. | | generateUniqueFilename | (filename) | string | Add timestamp to filename. | | buildStorageKey | (filename, options) | string | Build key with folder/prefix/suffix/timestamp. | | getAspectRatio | (width, height) | string | Calculate aspect ratio (e.g. "16:9"). | | calculateFitDimensions | (w, h, maxW, maxH) | {width, height} | Fit within bounds preserving ratio. | | generateResponsiveSizes | (width, height) | Array | Responsive breakpoint sizes. | | isValidDimensions | (w, h, constraints) | boolean | Validate against min/max dimensions. | | getKeyFromUrl | (url, publicUrl) | string | Extract storage key from full URL. | | groupFilesByFolder | (files) | object | Group file objects by folder path. |

How It Works

  1. Registration — The plugin validates all options at startup and creates an S3Client from @aws-sdk/client-s3. It stores the client, bucket name, public URL, and default ACL in a closure.

  2. Decoration — A storageMethods object is created with closures over the S3 client and config, then attached to the Fastify instance as fastify.xStorage.

  3. Uploadsupload() generates a storage key (with optional random suffix), detects MIME type via mime-types, and sends a PutObjectCommand. The returned object includes the key, public URL, size, and content type for database storage.

  4. Signed URLsgetSignedUrl() uses @aws-sdk/s3-request-presigner to create time-limited download URLs for private files. getSignedUploadUrl() does the same for direct client-to-S3 uploads, bypassing the server for large files.

  5. Batch operationsuploadMultiple() delegates to upload() with Promise.all. deleteMultiple() uses DeleteObjectsCommand for efficient batch deletion in a single S3 API call.

  6. Error handling — All methods validate inputs before touching S3 and wrap S3 errors with [xStorage] prefix and the affected key for debuggability.

License

UNLICENSED