@uploadcn/server

A Web-standard upload route for S3, R2 and S3-compatible storage.

npm install @uploadcn/server

Runs on Node.js, Bun, Deno, Cloudflare Workers and edge runtimes. Signing uses aws4fetch (Web Crypto, ~2.5 KB), no AWS SDK.

createUploadRoute

const { POST } = createUploadRoute(options)
// POST(request: Request): Promise<Response>

Prop

Type

Errors thrown as UploadRouteError(message, status, code?) are returned as { error } (plus code) with that status. code: "rejected" marks the file itself as refused: the client shows it as rejected and never retries. Anything else becomes a generic 500 and is logged, internal details never reach the client.

Storage

s3Storage

s3Storage({
  bucket, region, accessKeyId, secretAccessKey,
  sessionToken?, endpoint?, forcePathStyle?, publicUrl?, readUrlExpiresIn?,
})

Without publicUrl, url in results is a signed GET URL.

r2Storage

r2Storage({ accountId, bucket, accessKeyId, secretAccessKey, publicUrl?, jurisdiction? })

createMemoryStorage

const { storage, handler } = createMemoryStorage({ baseUrl: "/api/storage", secret })

A development storage implementing the full presigned and multipart flow with HMAC-signed URLs. Bytes are discarded (only size and ETag are kept). Mount handler at baseUrl for PUT requests. Not for production.

Custom storage

Implement UploadStorage (presignPut, createMultipart, presignPart, listParts, completeMultipart, abortMultipart, headObject, deleteObject, getUrl) to support any backend. Add the optional getObject(key) (a ReadableStream) to enable scanning and OCR of stored files.

Virus scanning

import { clamavScanner, createScanner, httpScanner, virusTotalScanner } from "@uploadcn/server/scan"

Each returns a Scanner whose scan({ stream, file, key?, signal? }) resolves to { status: "clean" | "infected" | "unknown", threats, scanner, details? }. See Virus scanning.

OCR

import {
  awsTextractOcr, azureDocumentIntelligenceOcr, createOcrProvider, createOcrRoute,
  googleVisionOcr, httpOcr, mistralOcr,
} from "@uploadcn/server/ocr"

Each provider's recognize({ data, type, name?, signal? }) resolves to an OcrResult. createOcrRoute({ provider, authorize?, maxFileSize?, allowedTypes?, storage?, onResult? }) returns { POST }. See OCR.

Protocol

One POST endpoint with a JSON body, dispatched on action. Implement it in any language to use s3Adapter with a non-JavaScript backend.

ActionRequestResponse
presign{ file: { name, type, size }, meta? }{ key, url, method: "PUT", headers }
complete{ key, file, meta? }{ key, url, etag?, data? }
create-multipart{ file, meta? }{ key, uploadId }
sign-part{ key, uploadId, partNumber }{ url }
list-parts{ key, uploadId }{ parts: [{ partNumber, etag, size }] | null }
complete-multipart{ key, uploadId, parts: [{ partNumber, etag }], file, meta? }{ key, url, etag?, data? }
abort-multipart{ key, uploadId }{ ok: true }

Errors: any non-2xx status with { "error": "message" }. 408, 429 and 5xx are retried by the client.

On this page