anonshare

The problem

Most file-sharing tools require an account, track usage, or bury access rules behind paywalls. Sometimes the real need is smaller and more immediate: send one file, explain how it should behave, and move on.

anonshare lets anyone upload a file, configure access rules such as one-time download, expiration, and preview, then receive a shareable link. The recipient opens the link and sees exactly what is available without needing an account on either side of the exchange.

This project is intentionally non-commercial. It exists to validate a Bun-first stack in a realistic product with storage, jobs, abuse controls, and admin operations while also being understandable in a portfolio review.

Who it serves

The page is written for four distinct readers: the person sharing a file, the person opening the link, the solo operator running the system, and the reviewer trying to assess the product and architecture quickly.

Primary audienceAnonymous uploader

Needs to share one file quickly without creating an account or handing over personal data.

Outcome: upload-first flow with one-time, expiration, and preview rules set before submission.

Primary audienceRecipient

Needs a clear answer when opening a link: preview, download, expired, hidden, deleted, or already consumed.

Outcome: public file pages make state explicit instead of collapsing every failure into a generic error.

OperatorSingle admin

Needs moderation, lifecycle visibility, queue health, and storage awareness in one place without building a support team around the product.

Outcome: a GitHub-protected dashboard with triage, queue telemetry, and anomaly review.

Portfolio reviewerEngineer or recruiter

Needs to understand what the product does and why the architecture looks the way it does, without reading the whole repo first.

Outcome: this page turns implementation choices into an intentional, interview-ready narrative.

What v1 optimizes for

v1 is intentionally narrow. The target is not feature breadth; it is a fast anonymous flow, clear recipient state, and an operating model that one person can realistically understand and maintain.

Product goalFast anonymous sharing

Keep the path from landing page to copyable link short, while still making access rules explicit before upload begins.

Product goalTrustworthy file state

Make recipients understand what happened to a file: available, expired, hidden, deleted, or consumed. State clarity matters as much as the download itself.

Operational goalSolo-operable lifecycle

Favor boundaries, observability, and repairability so one person can run the system without silent failure becoming the default.

System flow

From upload to cleanup, the system moves a file through a defined lifecycle with explicit transitions rather than informal state hidden inside request handlers.

01Upload

Validate file, MIME type, size, and options.

02Store

Persist metadata in PostgreSQL and stream the object into S3-compatible storage.

03Share

Generate an unguessable link with the configured access rules.

04Consume

Download or preview, with one-time access enforced through a backend-controlled path.

05Lifecycle

Expiration jobs, cleanup, and reconciliation maintain correctness over time.

06Moderate

Reports, auto-hiding, and admin review reduce abuse exposure.

Architecture

The monorepo splits responsibilities across three independently runnable processes and three shared packages. That boundary is deliberate: apps do not import each other, and shared rules live in packages instead of drifting across runtime surfaces.

Processapps/web
Public UI · Admin shell

TanStack Start with SSR. Serves the upload form, share pages, about page, and admin dashboard. The web app can consume shared contracts and domain helpers for display and validation, while mutations still go through the API boundary.

Processapps/api
Domain HTTP boundary

Hono on Bun. Handles upload, download, report, admin, and internal endpoints. Validates requests, orchestrates storage, and enforces access rules.

Processapps/worker
Async lifecycle work

BullMQ consumers on Bun. Processes expiration, cleanup, one-time post-download removal, and periodic reconciliation jobs. The reconciler is decomposed into independent passes (orphan detection, expired cleanup, pending promotion, one-time cleanup, terminal jobs, stale expiration, and cursor advancement) so each concern can be tested, retried, and observed in isolation.

Packagepackages/domain
Business rules

Pure TypeScript state transitions, file status rules, and validation invariants. No infrastructure dependencies.

Packagepackages/contracts
Shared types & schemas

Zod schemas for API requests and responses, error codes, and job payloads. Consumed by web, API, and worker without duplication.

Packagepackages/infrastructure
Platform primitives

Database connection, Drizzle schema and migrations, Redis client, S3-compatible storage adapter, rate limiter, logger, and config validation.

Stack choices

Each tool was selected for a specific operating reason, not just because it is currently fashionable. The stack is part of the product story.

Bun

Single runtime, package manager, bundler, and test runner. It reduces toolchain fragmentation and keeps the feedback loop fast enough for a solo project.

TanStack Start

SSR, file-based routing, and type-safe loaders let the same web surface own public routes, share pages, and the admin shell without a second frontend architecture.

Hono

Lightweight HTTP boundary with clean middleware semantics and strong Bun support. The API stays explicit instead of disappearing into framework magic.

PostgreSQL + Drizzle

Relational storage with strong consistency guarantees for file state, reports, sessions, and anomalies. Drizzle keeps schema and migrations close to the code.

Redis + BullMQ

Redis backs rate limiting, queue state, and fast operational lookups. BullMQ handles delayed expiration, retries, cleanup, and recurring reconciliation.

S3-compatible storage

Provider-agnostic object storage through Bun's native S3 API. MinIO works locally and production can target AWS S3 or Cloudflare R2 without rewriting the domain layer.

GitHub OAuth

A single allowlisted GitHub identity protects the admin surface. That matches the real operating model better than inventing a multi-user auth system early.

Operational controls

The system is not presented as a toy upload demo. It includes the minimum operational posture needed to make anonymous sharing believable: observability, health checks, abuse limits, and a dashboard that surfaces lifecycle issues instead of hiding them.

ObservabilityStructured logs with request correlation

API, worker, and operational scripts emit event-oriented logs with requestId, actor, entity, and outcome whenever those fields exist.

ReadinessHealth checks that probe real dependencies

GET /health and bun run infra:check verify PostgreSQL, Redis, and storage contracts instead of assuming container health means application readiness.

Abuse controlRate limiting on risky public surfaces

Upload, report, and repeated link access are Redis-backed and degrade safely when Redis is temporarily unavailable, preserving UX where feasible.

Operator UXQueue and anomaly visibility in the dashboard

The admin surface exposes queue lag, failed jobs, report pressure, and lifecycle anomalies so moderation and operational issues do not stay invisible. System settings such as the rate-limit degraded flag are surfaced separately so an operator can tell at a glance when a backing service is running in a reduced-capability mode.

VerificationService-backed browser E2E in CI

Playwright tests run against the full local stack (web + API + worker + all dependencies) in CI. They cover the anonymous upload flow, preview and download gating on share pages, report-triggered auto-hide, and authenticated admin moderation and pagination flows — the cross-process behaviors that unit tests cannot reach.

Key decisions & trade-offs

One-time download uses a backend-controlled path

Blind presigned URLs cannot guarantee atomic consumption. Retries, partial transfers, and concurrent requests create race conditions, so the backend reserves consumption before streaming the file.

Trade-off: Higher backend involvement than presigned-only delivery, but it preserves the one-time promise instead of approximating it.

Storage is provider-agnostic

The adapter targets S3-compatible APIs through Bun's native S3 support. MinIO runs locally, while R2 or S3 can back production without vendor-specific domain logic.

Trade-off: Fewer vendor-specific optimizations up front, but lower lock-in and simpler portability.

Reconciliation is a first-class concern

Delayed jobs are necessary but not sufficient. A recurring reconciler repairs missed expirations, orphaned objects, and metadata-storage drift instead of hoping queues never miss.

Trade-off: Adds scheduler and anomaly-management overhead, but keeps correctness from depending on one happy-path job execution.

Auto-hide favors containment over certainty

Anonymous upload raises moderation risk quickly. Files become hidden after a configurable report threshold so the public surface reacts before an admin can manually review every case.

Trade-off: False positives remain possible, so restore is a first-class admin action rather than an afterthought.

Web, API, and worker stay separate

The web serves UI, the API owns domain mutations, and the worker owns asynchronous lifecycle work. That keeps the system understandable as it grows.

Trade-off: Operationally heavier than a single process, but much harder to entangle by accident.

Known limitations

These are explicit limitations of the shipped system, not fine print. They explain why the roadmap looks the way it does and prevent the page from promising more than the product actually delivers today.

Known limitationUploads are server-mediated in v1

The initial upload path prioritizes observability and simpler consistency handling over the lower bandwidth cost of direct presigned uploads.

Known limitationPreview is intentionally narrow

Only supported MIME types are previewable, and one-time files never expose preview because that would undermine single-consumption semantics.

Known limitationModeration is intentionally simple

Auto-hide is threshold-based and operated by one admin. It is designed for fast containment in a solo project, not for enterprise review workflows.

Known limitationThe product favors depth over breadth

anonshare is intentionally narrower than consumer file-sharing suites. The point of v1 is to make trade-offs explicit, not to imitate every adjacent product feature.

What's deliberately not in v1

These exclusions are scoping decisions, not accidental omissions. They keep the first version coherent instead of turning it into a grab-bag of adjacent features.

Multi-user accountsAnonymity is the core UX for uploaders and recipients.
Billing & subscriptionsThe project is non-commercial and optimized for hobby-budget infrastructure.
End-to-end encryptionClient-side key management would materially complicate the anonymous flow and recovery model.
Malware scanningUseful, but it requires an extra scanning service and a different activation model than v1 currently ships.
Password-protected sharesThat adds auth-like friction to a product whose main value is a zero-onboarding share path.
Multi-admin supportThe operating model is one allowlisted GitHub identity, not a team console.
Folder & collaboration featuresanonshare is a single-file, single-link workflow rather than a workspace product.

Possible next steps

These are informed directions after v1, not promises. Each one changes cost, consistency, or product complexity, so the impact matters as much as the idea itself.

Presigned uploads

Move large file bodies off the application server for lower latency and lower bandwidth cost.

Impact: Would improve throughput, but requires a tighter activation handshake so metadata and storage remain consistent.

Malware scanning

Scan uploads before activation to reduce abuse and operator risk.

Impact: Improves trust and moderation posture, but adds a new service dependency and longer activation paths.

Password-protected links

Add an optional passphrase layer to public share links.

Impact: Raises recipient-side protection, but changes the current open-link-immediately product shape.

Richer download analytics

Expand per-file telemetry for the admin dashboard beyond counts and queue signals.

Impact: Improves investigation and capacity planning, but increases data-retention and privacy-design decisions.