A multi-user file manager built with Node.js and Express. Users sign up, organize files into folders, upload by drag and drop, and download or delete them. Files live in Supabase Storage, and each user can only see their own.
I built this to practice full-stack fundamentals end to end: authentication, relational data modeling, file storage, and the security details that are easy to skip in a tutorial project.
Live demo — hosted on Render's free tier, so the first load after a period of inactivity can take about a minute to wake up.
| Dark mode | Mobile |
|---|---|
![]() |
![]() |
- Account signup and login with hashed passwords (bcrypt) and Passport sessions stored in Postgres
- Folders: create, rename, delete (deleting a folder also removes its stored files)
- Drag-and-drop or browse upload with a progress bar and multi-file support
- File cards showing type, size, and upload date; download and delete with a confirmation dialog
- Responsive layout with a mobile drawer, automatic dark mode, and toast notifications
- Accessible basics: keyboard-operable upload area, focus styles, labelled controls, reduced-motion support
| Area | Choice |
|---|---|
| Server | Node.js, Express 4 |
| Views | Pug, one shared stylesheet with design tokens, vanilla JavaScript (no framework) |
| Auth | Passport (local strategy), express-session, bcryptjs |
| Data | PostgreSQL via Prisma ORM, sessions stored with @quixo3/prisma-session-store |
| File storage | Supabase Storage |
| Validation | express-validator, multer |
flowchart LR
B[Browser<br/>Pug pages + JS] -->|HTTPS, session cookie, CSRF token| E[Express app on Render<br/>routes, controllers, middleware]
E -->|Prisma queries scoped to the user| D[(PostgreSQL on Supabase<br/>users, folders, files, sessions)]
E -->|upload / download after ownership check| S[(Supabase Storage<br/>file bytes)]
File bytes never go straight from the browser to storage. Every upload and download passes through Express, which confirms the file belongs to the logged-in user first, so the storage bucket can stay private.
- Ownership checks on every query. Files and folders are always looked up through the logged-in user, so guessing another user's ID returns a 404 instead of their data.
- CSRF protection. State-changing requests need a per-session token. Forms send it in a hidden field and the upload script sends it as a header. Deletes and logout are POST requests, never links.
- Safe uploads. Files are stored under unique keys (
<userId>/<uuid>-<name>) so same-named files never overwrite each other. Uploads are size-limited and streamed from memory, so no temp files are left behind. - Private bucket. Downloads go through the app, which checks ownership first, so the storage bucket can stay private.
- Auth hygiene. Passwords are never trimmed or escaped, login errors are generic so usernames can't be probed, login attempts are rate limited, and session cookies are
httpOnlyandsameSite. - Strict CSP. No inline scripts or styles, so a Content-Security-Policy header can block injected code.
Requirements: Node 18+, a PostgreSQL database, and a Supabase project with a storage bucket (all available on free tiers).
git clone https://github.com/davidsan13/fileUploader.git
cd fileUploader
npm install
cp .env.example .env # fill in the values
npx prisma generate
npx prisma db push
npm run dev # http://localhost:3000Environment variables (see .env.example):
| Variable | Purpose |
|---|---|
DATABASE_URL |
Postgres connection string |
SESSIONSECRET |
Long random string for signing session cookies |
SUPA_URL, SUPABASE_KEY |
Supabase project URL and service key (server-side only) |
SUPABASE_BUCKET |
Storage bucket name (default newFiles) |
MAX_UPLOAD_MB |
Per-file upload limit (default 25) |
app.js, bin/www App setup (sessions, passport, CSRF, security headers) and server entry
routes/ /users, /folders, /files (folder and file routes require login)
controllers/ Request handlers, all scoped to the current user
middleware/ Auth guards, CSRF, flash messages, rate limiting, passport strategy
lib/ Shared Prisma client, Supabase storage helper, formatting helpers
views/, public/ Pug templates, stylesheet, client-side script
prisma/schema.prisma Data model: User, Folder, File, Session
- A working login is only the start of auth. The real work was making every folder and file query scoped to the logged-in user, adding CSRF tokens, and keeping login errors generic so usernames can't be probed.
- Configuration bugs are easy to miss. Loading
.envafter other modules had already read it, and a mis-formattedSUPABASE_BUCKETvalue, both caused failures that looked unrelated to the real cause. - Keep migrations in sync with the schema. My first migration drifted from
schema.prisma, so setup usesprisma db pushuntil I re-baseline it. - Shared CSS and JavaScript pay off. Moving duplicated inline styles and scripts into shared files made the UI consistent and allowed a strict Content-Security-Policy.
- Automated tests in the repo (I checked auth, CSRF, and cross-user access against an in-memory fake database, but haven't committed a test suite yet)
- Baseline the Prisma migrations so setup uses
migrateinstead ofdb push - Nested folders, file rename and move, and shareable expiring links
- Image previews and a per-user storage quota


