Skip to content

ashlin-a/byte-bay

Repository files navigation

🌊 ByteBay

Warning

This project is under active development. APIs, schema, and features may change without notice. Not production-ready.

ByteBay is a modern, high-performance cloud storage application (a Google Drive clone). Built as a Turborepo monorepo with a React frontend and an Express backend, powered entirely by the Bun runtime.

Tech Stack

Layer Technology
Runtime & Package Manager Bun
Monorepo Orchestration Turborepo
Frontend React 19 + Vite 7 (SWC) + TypeScript
Docs Site Astro 6 + Starlight
Routing TanStack Router v1 (file-based)
Server State TanStack Query v5
Styling Tailwind CSS v4 + Wabi-Sabi theme
Backend Express 5 + TypeScript + Bun
Database PostgreSQL + Drizzle ORM
Authentication Better Auth
File Storage AWS S3 / RustFS (local dev)
Validation Zod v4
API Testing Bruno (@usebruno/cli)
Unit & Integration Tests Bun test runner + supertest
Code Quality ESLint 9 (Flat Config), Prettier, Husky, lint-staged

Project Structure

byte-bay/
├── apps/
│   ├── api/        — Express 5 backend (:3000)
│   │   └── src/
│   │       ├── config/      — DB, auth, S3 config
│   │       ├── middlewares/
│   │       ├── utils/
│   │       │   ├── errors.ts   — HttpError base + NotFoundError, GoneError, ConflictError
│   │       │   └── session.ts  — getSession(res) helper for controllers
│   │       └── modules/     — feature modules (auth, users, files, folders, …)
│   ├── client/     — React 19 + Vite frontend (:5173)
│   │   └── src/
│   │       ├── lib/         — auth client, API helpers, TanStack Query client, tokens, focus-trap
│   │       ├── routes/      — file-based TanStack Router routes
│   │       └── components/  — reusable UI components (Wabi-Sabi themed)
│   └── home/       — Astro + Starlight docs site (:4321 dev, :8080 docker)
└── packages/
    ├── eslint-config/       — shared ESLint flat configs
    ├── typescript-config/   — shared tsconfig bases
    └── ui/                  — shared React component library

Getting Started

Prerequisites

Install Bun:

curl -fsSL https://bun.sh/install | bash

Installation

bun install

Environment Variables

cp apps/api/.env.example apps/api/.env

Fill in apps/api/.env:

PORT=3000
DATABASE_URL=postgres://postgres:postgres@localhost:5432/bytebay
BETTER_AUTH_SECRET=   # generate: openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
CORS_ORIGIN=http://localhost:5173

AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
S3_BUCKET=
# For local dev with RustFS:
AWS_ENDPOINT_URL=http://localhost:9000
AWS_PRESIGN_ENDPOINT_URL=http://localhost:9000
PRESIGNED_URL_TTL=3600

Running the Project

With Docker (recommended)

Starts postgres, RustFS, API, client, and docs together:

docker compose up --build

Migrations — after first boot, run:

cd apps/api && DATABASE_URL=postgres://postgres:postgres@localhost:5432/bytebay bun run db:migrate

RustFS — create the bytebay bucket on first boot. Two options:

  • Web console at http://localhost:9001 (credentials: rustfs / rustfs123)
  • One-shot script:
    bun -e "
    import { S3Client, CreateBucketCommand } from '@aws-sdk/client-s3';
    const s3 = new S3Client({ endpoint: 'http://localhost:9000', region: 'us-east-1',
      credentials: { accessKeyId: 'rustfs', secretAccessKey: 'rustfs123' }, forcePathStyle: true });
    await s3.send(new CreateBucketCommand({ Bucket: 'bytebay' }));"

The bucket lives in the rustfs_data docker volume — only re-create after docker compose down -v.

Service URL
Client http://localhost:80
Docs http://localhost:8080
API http://localhost:3000
PostgreSQL localhost:5432
RustFS S3 http://localhost:9000
RustFS UI http://localhost:9001

Local Development

Start a local Postgres instance, apply migrations, then:

bun run db:migrate   # from apps/api
bun dev              # from repo root
  • Client: http://localhost:5173
  • API: http://localhost:3000
  • Docs: http://localhost:4321
  • Vite proxies all /api/* requests to the API automatically.

Commands

From the repo root:

Command Description
bun dev Start all apps in watch mode
bun build Build all apps and packages
bun lint Run ESLint across the monorepo
bun format Format all files with Prettier
bun format:check Check formatting without writing
bun check-types Type-check all packages
bun run test Run unit + integration tests (needs Docker)
bun run test:unit Unit tests only — no Docker required

From apps/api:

Command Description
bun run test:unit Unit tests only
bun run test:integration Integration tests only
bun run test:coverage Tests with coverage report
bun run db:generate Generate Drizzle migration files
bun run db:migrate Apply migrations to the database
bun run db:studio Open Drizzle Studio

Never run bare bun test from the repo root or apps/api. It triggers a Bun 1.3.13 mock.module isolation bug and causes ~16 unit test failures. Use bun run test or bun run test:unit instead.

API Architecture

The API follows a Feature-Based (Domain-Driven) architecture. Every module under src/modules/[feature]/ contains exactly five files:

File Responsibility
[feature].schema.ts Drizzle table definitions
[feature].interfaces.ts Types inferred from schema + DTOs
[feature].routes.ts Route declarations + middleware only
[feature].controller.ts HTTP layer — delegates to service
[feature].service.ts Business logic + DB queries

Scaffolded modules: auth, users, folders, files, file-versions, shared-links, audit-events.

Shared utilities in src/utils/:

  • errors.tsHttpError base class + generic NotFoundError, GoneError, ConflictError
  • session.tsgetSession(res) helper; use in controllers instead of inline res.locals cast

Validation

Request bodies validated with Zod v4 via shared validate middleware. Responses validated client-side via Zod schemas in src/lib/api.ts (axios + Zod).

router.post('/sign-up', validate(signUpSchema), authController.signUp);

Each module's [feature].interfaces.ts owns its Zod schemas and inferred input types.

Auth Endpoints

Method Path Auth Description
POST /api/auth/sign-up Public Register — validated by Zod
POST /api/auth/sign-in/email Public Sign in (Better Auth native)
POST /api/auth/sign-out Required Sign out
GET /api/auth/me Required Current session

Folders Endpoints

Method Path Auth Description
GET /api/folders Required List folders; optional ?parentFolderId= filter
POST /api/folders Required Create folder
GET /api/folders/:id Required Get single folder
PATCH /api/folders/:id Required Rename or move folder; body: { name? } or { parentFolderId? } (null = root); rejects circular moves
DELETE /api/folders/:id Required Soft delete (trash) — cascades to child folders and their files

Files Endpoints

Method Path Auth Description
POST /api/files Required Initiate upload — returns presigned PUT URL; mimeType validated against allowlist
GET /api/files Required List files (excludes trashed)
GET /api/files/:id Required Get single file
GET /api/files/:id/download-url Required Presigned download URL
PATCH /api/files/:id Required Move file; body: { folderId: string | null } (null = root)
DELETE /api/files/:id Required Soft delete (trash)

Shared Links Endpoints

Method Path Auth Description
POST /api/shared-links Required Create shared link for a file
GET /api/shared-links/:token Public Resolve token → file info + download URL
DELETE /api/shared-links/:id Required Revoke link

Audit Events Endpoints

Method Path Auth Description
GET /api/audit-events Required List caller's events; optional ?action=, ?fileId=, ?from=, ?to=, ?limit=, ?offset=

API Testing

The collection lives in bruno/. Run with Bruno CLI:

cd bruno && bunx bru run --env local            # full collection
cd bruno && bunx bru run 02-folders --env local # single folder

Folder order matters — Bruno executes folders alphabetically:

Folder Contents Vars produced
01-auth/ sign-up, sign-in, me sessionCookie, testEmail
02-folders/ create, list, get, rename, create-child, move, trash folderId, childFolderId
03-files/ initiate-upload, list, get, download-url, move fileId, uploadUrl
04-shared-links/ create, resolve (public — no auth), revoke sharedLinkToken, sharedLinkId
05-audit-events/ list
06-teardown/ trash-file, sign-out

Trash deferred to 06-teardown/ so 04-shared-links/ can resolve a live file. Sign-out last — doesn't invalidate session mid-run.

Secrets: bruno/.env (gitignored) holds TEST_PASSWORD. Copy from bruno/.env.example.

Testing

Unit tests live alongside each module. Integration tests use testcontainers to spin up real Postgres and MinIO containers — no mocks, real SQL, real presigned URLs, real Better Auth sessions.

Requires Docker running.

# From repo root (preferred):
bun run test        # unit + integration (needs Docker)
bun run test:unit   # unit only

# From apps/api:
bun run test:unit           # src/modules/ only
bun run test:integration    # src/integration/ only (starts containers)

Do not run bare bun test — see warning in Commands section above.

Git Workflow

Pre-commit (every commit):

  1. bun lint — ESLint across the repo
  2. lint-staged — Prettier on staged files
  3. bun check-types — TypeScript across all packages

Pre-push (before push):

  1. bun format:check — verify all files are formatted
  2. cd apps/api && bun run test:unit && bun test src/integration/ — full test suite

Any failing check aborts the operation.

About

ByteBay is a modern, high-performance cloud storage application (a Google Drive clone).

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors