Hype StackHypeStack

Storage

The template includes an S3-compatible object storage client. Locally it talks to RUSTFS (or MinIO-compatible) from Docker Compose. On deploy, Fly uses Tigris and Railway uses an S3-compatible bucket. The app code stays the same: change the endpoint and keys, not the upload calls.

Client

Initialized at backend startup from apps/backend/src/libs/storage/storage.ts:

ts
s3 = new S3Client({
  endpoint: process.env.RUSTFS_ENDPOINT,
  region: "us-east-1",
  credentials: {
    accessKeyId: process.env.RUSTFS_ACCESS_KEY,
    secretAccessKey: process.env.RUSTFS_SECRET_KEY,
  },
  forcePathStyle: true,
});

Helpers on that module cover upload, download, signed URLs, and delete. The client auto-creates the bucket on first upload when the provider allows it.

Environment variables

VariablePurpose
RUSTFS_ENDPOINTS3 API endpoint (local Docker URL or cloud endpoint)
RUSTFS_ACCESS_KEYAccess key
RUSTFS_SECRET_KEYSecret key
BUCKET_NAMEDefault bucket for app uploads

deploy wires these from the provider you pick for --bucket. For a database or bucket you already run, use the external block in stack.json (same pattern as bring-your-own Postgres).

Upload middleware

HTTP uploads go through apps/backend/src/middleware/file/:

ts
import { fileUpload, memoryStorage } from "@backend/middleware/file";

app.post(
  "/avatar",
  fileUpload({
    storage: memoryStorage(),
    limits: { fileSize: 5 * 1024 * 1024, files: 1 },
  }).single("file"),
  async (c) => {
    const file = c.get("file");
    // uploadFile({ bucket, key, body: file.buffer, contentType: file.mimetype })
    return c.json({ ok: true });
  },
);

Prefer diskStorage for larger files. Always set limits.fileSize and a fileFilter when only images (or another MIME set) are allowed.

What packs use it for

After a starter or feature pack install, storage typically backs:

  • User avatars
  • Organization logos
  • Team / project icons
  • Board or media images in collaboration packs

Those features import @backend/libs/storage/storage and the file middleware. They do not bring their own storage provider.

Local vs production

EnvironmentTypical backend
Local DockerRUSTFS / MinIO-compatible service in docker-compose.yml
Fly.io deployTigris (S3 API)
Railway deployRailway's S3-compatible bucket / RustFS service

Keep forcePathStyle: true unless your provider documents otherwise. Path-style addressing is what the local and deployed setups expect.

Sponsor open source

Every purchase and sponsorship funds my 8+ years of work on open source given freely to the community. It keeps the lights on, funds new packs, and keeps the ecosystem alive. Even a small tier means a lot. Thank you!

Sponsor on GitHub