Skip to content

Storage: local filesystem

The same Storage contract, backed by a directory on disk.

Use this for development, tests that want real I/O, and single-node deployments where shared object storage would be overkill.

Install

sh
pnpm add @nwire/storage @nwire/storage-fs

Wire it in

ts
import { createApp }     from "@nwire/app"
import { storagePlugin } from "@nwire/storage"
import { fsStorage }     from "@nwire/storage-fs"

export const app = createApp({
  appName: "my-app",
  plugins: [storagePlugin({
    storage: fsStorage({ baseDir: "./var/uploads" }),
  })],
})

The baseDir will be created automatically on first use (and at every readiness probe). Keys map to files under it: posts/2025/cover.png./var/uploads/posts/2025/cover.png.

Metadata

Content-type and arbitrary metadata are persisted in a sidecar .meta.json next to each file. Disable this if you don't need it:

ts
fsStorage({ baseDir: "./var/uploads", persistMetadata: false })

Limitations

  • No presigned PUT URLs. Clients can't upload directly to a server file path — url(key, { method: "put" }) throws StorageUnsupportedError. Mount an HTTP upload endpoint instead and stream into storage.put(...).
  • No sharing across nodes. Each replica sees only its own disk.
  • No CDN integration. Front it with a real CDN only after moving to S3.

When to use it

ScenarioUse fs?
Local dev
Tests with real I/O
Single VPS / Raspberry Pi
Multi-node deployment❌ — switch to S3
Need presigned URLs❌ — switch to S3

Key safety

Keys are resolved relative to baseDir and rejected if they'd escape it. storage.put("../escape.txt", ...) throws — the adapter refuses to write outside its sandbox.

MIT licensed.