A self-hosted file storage you actually own.
SELFIE is a self-hosted personal cloud storage platform built for simplicity and low-resource environments.
Unlike larger solutions, SELFIE focuses on:
- Minimal resource usage
- Simple deployment
- Mobile-hostable environments (Termux)
- No object storage dependency
- No Docker requirement
- Modern TypeScript codebase
- 🔐 Multi-user auth with JWT (access + refresh tokens)
- 👑 Role-based access —
USERandADMINroles; the first registered user automatically getsADMIN - ⬆️ Upload / ⬇️ Download / 🗑️ Delete files via REST API and Web UI
- 📊 Per-user storage quotas — enforced before upload (default 5 GB)
- 🔁 Automatic token rotation — seamless refresh on expiry
- 📱 Runs on Termux — minimal hardware, fully self-contained
- ☁️ Cloudflare Tunnel ready — expose securely without a public IP
| Layer | Technology |
|---|---|
| Runtime | Node.js 20+ |
| Framework | Hono — ultralight, fast |
| Language | TypeScript (strict mode) |
| ORM | Prisma 7 — type-safe database access |
| Database | PostgreSQL |
| Auth | JWT via jsonwebtoken + scryptSync (salted & peppered) |
| Client | Next.js 16 + React 19 + Tailwind CSS 4 |
| UI Kit | shadcn/ui (radix-nova) |
| Monorepo | Turborepo |
| Linting | Biome |
| Tunnel | Cloudflare Tunnel (optional) |
┌─────────────────┐ ┌───────────────────┐ ┌────────────┐
│ Next.js App │──────▶│ Hono API Server │──────▶│ PostgreSQL │
│ (apps/client) │ HTTP │ (apps/server) │ SQL │ │
│ Port 3000 │◀──────│ Port 3001 │◀──────│ │
└─────────────────┘ └────────┬──────────┘ └────────────┘
│
▼
┌────────────────┐
│ File Storage │
│ ./uploads/ │
└────────────────┘
The monorepo contains two apps orchestrated by Turborepo:
apps/client— Next.js dashboard UI (login, signup, file browser, storage overview)apps/server— Hono REST API (auth, file CRUD, user management, health checks)
File uploads are stored on disk under STORAGE_DIR (default ./uploads/). Metadata, user accounts, and sessions live in PostgreSQL.
SELFIE/
├── apps/
│ ├── client/ # Next.js frontend
│ │ ├── src/
│ │ │ ├── app/
│ │ │ │ ├── (auth)/ # Login & Signup pages
│ │ │ │ │ ├── login/
│ │ │ │ │ └── signup/
│ │ │ │ ├── (dashboard)/ # Dashboard pages
│ │ │ │ │ └── dashboard/ # Overview & Storage views
│ │ │ │ └── layout.tsx # Root layout (dark theme, Inter font)
│ │ │ ├── components/
│ │ │ │ ├── ui/ # shadcn/ui primitives
│ │ │ │ ├── DashboardSidebar.tsx
│ │ │ │ └── DashboardTopbar.tsx
│ │ │ ├── contexts/UserContext.tsx
│ │ │ ├── hooks/useUser.ts
│ │ │ ├── lib/api.ts # Axios client with all API calls
│ │ │ └── types/API.ts # Shared TypeScript types
│ │ └── public/
│ └── server/ # Hono REST API
│ ├── prisma/
│ │ ├── migrations/ # 4 database migrations
│ │ └── schema.prisma # User, Session, File models
│ ├── src/
│ │ ├── controllers/ # Route handlers
│ │ ├── lib/ # Utilities (JWT, crypto, config)
│ │ ├── middleware/ # Auth & admin middleware
│ │ ├── routes/ # Route definitions
│ │ ├── services/ # Business logic (auth, quota, files)
│ │ └── types/ # Hono extension types
│ └── nodemon.json
├── packages/ # (empty — for future shared packages)
├── biome.json # Biome linter/formatter config
├── package.json # Monorepo root (Turborepo)
├── pnpm-workspace.yaml # Workspace definition
├── turbo.json # Turborepo pipeline config
└── .env.example files # Environment templates
-
Node.js 20+
-
pnpm (
npm i -g pnpmor other alternatives) -
PostgreSQL — local, remote, or via Termux
-
If you're going to use tunnels (to make your storage accessible from anywhere):
- A cloudflare account
- Cloudflared CLI installed in your system
git clone https://github.com/ewwhenry/SELFIE
cd SELFIE
pnpm installRun the interactive setup script:
pnpm run setupIt will prompt for your domain, encryption secrets, ports, and Cloudflare tunnel setup.
After the script completes, edit apps/server/.env to set your DATABASE_URL.
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
— | PostgreSQL connection string |
JWT_SECRET |
"your_jwt_secret" |
Secret for signing JWT access tokens |
CRYPT_SECRET |
— | Pepper for password hashing (scrypt) |
PORT |
3001 |
Server listen port |
NODE_ENV |
"development" |
Set to "production" in production |
DOMAIN |
— | Your domain (used for CORS & cookies) |
STORAGE_DIR |
"./uploads" |
Directory for uploaded files |
Access tokens: 15 min TTL · Refresh tokens: 30 day TTL
| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_API_URL |
"http://localhost:3001" |
Base URL of the API server |
cd apps/server
pnpm exec prisma migrate dev --name init
pnpm exec prisma generateThis creates the required tables: User, Session, and File.
# Development (both client & server concurrently)
pnpm dev
# OR
# Production build
pnpm build
# Production start
pnpm startTurborepo is not currently supported on Android.
Run each application separately:
apps/client
pnpm build
pnpm startapps/server
pnpm build
pnpm startThe compilation (build) process may take a while due to Android limitations. I'll try to find a way to speed it up as much as possible. For now, please be patient and wait until the compilation process finish.
If you find any way to accelerate this process on Android, please let me know by email or open a PR. Thanks.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/health |
— | Returns { message: "API is UP" } |
GET |
/health/db |
— | Database connectivity check with response time |
All auth endpoints return access_token and refresh_token as httpOnly cookies.
| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/auth/register |
— | Register new user. Body: { email, password, first_name, last_name } |
POST |
/auth/login |
— | Login. Body: { email, password } |
POST |
/auth/refresh |
— | Refresh tokens. Body: { refresh_token }, you probably will not need this |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/users/me |
Cookie | Get current user profile (id, role, email, name, quota, usage) |
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/files |
Cookie | List user's files (?cursor=&limit=10, cursor-based pagination) |
POST |
/files/upload |
Cookie | Upload file (multipart form, field: file). Enforces quota. |
GET |
/files/:file_id/download |
Cookie | Stream file download with Content-Disposition |
DELETE |
/files/:file_id |
Cookie | Delete single file |
DELETE |
/files |
Cookie | Batch delete (body: array of file IDs) |
- Login/Register — server sets
access_token(15 min) andrefresh_token(30 days) as httpOnly cookies - Token rotation — when
access_tokenexpires, the auth middleware automatically rotates both tokens usingrefresh_token - Logout — clearing cookies suffices; sessions remain in DB (TTL-based cleanup planned)
| Command | Description |
|---|---|
pnpm dev |
Start both apps in development mode (Turborepo) |
pnpm build |
Build both apps for production |
pnpm start |
Start production builds |
pnpm lint |
Lint all files with Biome |
pnpm format |
Format all files with Biome |
pnpm check |
Check all files with Biome |
pnpm check:fix |
Auto-fix all Biome issues |
pnpm tunnel |
Starts the tunnel configured in the setup process |
- Public share links
- Virtual folders
- Admin dashboard
- Sync client
- CLI
- File previews
MIT
