ZZuro Docs

Uploads

Published Oct 5, 2025 | Updated Mar 13, 2026

Add production-ready uploads with S3, R2, or Cloudinary

Overview

Add production-ready file upload flows (proxy, direct, multipart) to your backend.


Install

npx zuro add uploads

Scaffolds upload routes/controllers/libs, injects route mounting, and writes provider-specific env variables.


What This Generates

src/
├ controllers/uploads.controller.ts
├ routes/uploads.routes.ts
├ middleware/upload-auth.ts
└ lib/uploads/
  ├ index.ts
  ├ types.ts
  ├ provider.ts
  ├ metadata.ts
  └ proxy.ts

src/db/schema/uploads.ts   # only when metadata storage is enabled
  • controllers/uploads.controller.ts: input validation + HTTP handlers.
  • routes/uploads.routes.ts: mode-specific endpoints under /api/uploads.
  • middleware/upload-auth.ts: required/public upload access gate.
  • lib/uploads/provider.ts: provider implementation (S3/R2/Cloudinary).
  • lib/uploads/metadata.ts: metadata persistence or noop adapter.
  • lib/uploads/proxy.ts: multer middleware for proxy uploads.

Quick Example

Proxy mode request:

POST /api/uploads
Content-Type: multipart/form-data
file=<binary>
curl -X POST http://localhost:3000/api/uploads \
  -F "file=@./avatar.png"

Example response:

{
  "upload": {
    "key": "uploads/public/2026/03/12/uuid-avatar.png",
    "provider": "s3",
    "access": "public",
    "originalName": "avatar.png",
    "mimeType": "image/png",
    "bytes": 12345,
    "url": "https://..."
  },
  "record": null
}

How It Works

  1. Client calls an uploads endpoint.
  2. Route in /api/uploads/* forwards to UploadsController.
  3. Controller validates input and calls lib/uploads service functions.
  4. Service talks to provider client (S3, R2, or Cloudinary).
  5. Metadata is optionally stored.
  6. Standardized upload response is returned.

Configuration

UPLOAD_PROVIDER=s3
UPLOAD_MODE=direct
UPLOAD_AUTH_MODE=required
UPLOAD_FILE_ACCESS=private
UPLOAD_FILE_PRESET=image
UPLOAD_KEY_PREFIX=uploads
UPLOAD_ALLOWED_MIME=image/jpeg,image/png,image/webp,image/gif
UPLOAD_MAX_FILE_SIZE=5242880
UPLOAD_MAX_FILES=1
UPLOAD_DIRECT_URL_TTL_SECONDS=900
UPLOAD_ACCESS_URL_TTL_SECONDS=300
UPLOAD_MULTIPART_PART_SIZE=5242880
UPLOAD_BUCKET=my-bucket
UPLOAD_REGION=us-east-1
UPLOAD_ENDPOINT=
UPLOAD_ACCESS_KEY_ID=...
UPLOAD_SECRET_ACCESS_KEY=...
UPLOAD_PUBLIC_BASE_URL=
  • UPLOAD_PROVIDER: s3, r2, or cloudinary.
  • UPLOAD_MODE: upload flow (proxy, direct, large).
  • UPLOAD_AUTH_MODE: who can upload (required or none).
  • UPLOAD_FILE_ACCESS: resulting file access level (private or public).
  • UPLOAD_FILE_PRESET: preset defaults for allowed MIME and limits.
  • UPLOAD_KEY_PREFIX: storage key prefix.
  • UPLOAD_ALLOWED_MIME: comma-separated allowed MIME types.
  • UPLOAD_MAX_FILE_SIZE: max bytes per file.
  • UPLOAD_MAX_FILES: max files accepted by proxy middleware.
  • UPLOAD_DIRECT_URL_TTL_SECONDS: expiry for direct upload signed URLs.
  • UPLOAD_ACCESS_URL_TTL_SECONDS: expiry for private access URLs.
  • UPLOAD_MULTIPART_PART_SIZE: suggested part size for multipart uploads.
  • UPLOAD_BUCKET: S3/R2 bucket name.
  • UPLOAD_REGION: S3 region or auto for R2.
  • UPLOAD_ENDPOINT: custom endpoint (used for R2/S3-compatible storage).
  • UPLOAD_ACCESS_KEY_ID: provider access key.
  • UPLOAD_SECRET_ACCESS_KEY: provider secret key.
  • UPLOAD_PUBLIC_BASE_URL: optional CDN/public base URL.

Cloudinary keys:

CLOUDINARY_CLOUD_NAME=...
CLOUDINARY_API_KEY=...
CLOUDINARY_API_SECRET=...
CLOUDINARY_FOLDER=uploads
CLOUDINARY_UPLOAD_PRESET=
  • CLOUDINARY_CLOUD_NAME: Cloudinary account cloud name.
  • CLOUDINARY_API_KEY: Cloudinary API key.
  • CLOUDINARY_API_SECRET: Cloudinary API secret.
  • CLOUDINARY_FOLDER: destination folder for uploads.
  • CLOUDINARY_UPLOAD_PRESET: optional preset for direct uploads.

API Reference

  • Proxy mode:
  • POST /api/uploads: upload multipart file through your API server.
  • Direct mode:
  • POST /api/uploads/presign: create signed upload URL/params.
  • POST /api/uploads/complete: finalize direct upload and persist metadata.
  • Large mode (S3/R2):
  • POST /api/uploads/multipart/init: start multipart upload.
  • POST /api/uploads/multipart/complete: complete multipart upload.
  • POST /api/uploads/multipart/abort: abort multipart upload.
  • All modes:
  • POST /api/uploads/access-url: get access URL for stored key.
  • DELETE /api/uploads: delete uploaded file and metadata.

Advanced Usage

Private mode:

UPLOAD_AUTH_MODE=required
UPLOAD_FILE_ACCESS=private

Custom proxy route integration:

import { Router } from "express";
import { uploadSingle } from "../lib/uploads/proxy";
import { saveProxyUpload } from "../lib/uploads";

const router = Router();

router.post("/products", uploadSingle("image"), async (req, res) => {
  const result = await saveProxyUpload(req.file!, { ownerId: "user_123" });
  res.status(201).json({ imageKey: result.upload.key });
});

Database metadata integration:

npx zuro add database
npx zuro add uploads

This adds src/db/schema/uploads.ts and stores upload records in your database.


Example Use Cases

  • User profile/avatar uploads with private access URLs.
  • Direct browser uploads for large media-heavy applications.
  • Document management APIs with MIME and file-size enforcement.
  • Webhook-driven ingestion pipelines that persist upload metadata.

On this page