A file storage app with folders and authentication, built with Express and PostgreSQL, using a self-hosted, S3-compatible object store for file data.
Requires Docker and Docker Compose. No local Node, Postgres, or storage server installation needed as everything runs in containers.
git clone https://github.com/NicoCodesCode/file-uploader.git
cd file-uploaderCopy .env.example into an actual .env file, open it and fill in real values for each variable (any local values work, these aren't shared with anything external).
docker compose up --buildThe app is now available at http://localhost:3000. Sign up for a new account to try it. Uploads, downloads, folders, and deletes are all fully functional against the local Garage container.
- Runtime: Node.js, Express
- Database: PostgreSQL, via Prisma ORM
- Object storage: Garage, a self-hosted, S3-API-compatible storage server
- Auth: Passport (local strategy) + bcrypt, with sessions persisted in Postgres via
prisma-session-store - Views: EJS
- Testing: Jest + Supertest
- Containerization: Docker, Docker Compose
- Full auth flow
- Route-level auth protection
- Nested folder structure
- File upload, download, and delete backed by real S3-API object storage, not local disk
- Fully containerized local environment
- Automated tests
The Express app is the only thing the browser talks to. It stores structured data like users, sessions and folder/file metadata in Postgres via Prisma, and stores actual file bytes in Garage over the S3 API, using the minio npm package as an S3-compatible client. All three services run as separate containers on one Docker Compose network, addressing each other by service name (db, garage) rather than localhost.
npm testTests run against a separate file_uploader_test database, created automatically the first time the Postgres container initializes (see docker/init-test-db.sh), so they never touch the main development database. Test env vars are swapped in before any test file loads (jest.setup.js), and the Prisma connection is explicitly closed after the full run (jest.teardown.js).
Tests run with --forceExit. This is intentional, not a workaround for a bug: prisma-session-store keeps a recurring background timer alive for its own session-cleanup logic, which is outside the app's control and has no effect on test correctness — it just keeps the process from exiting on its own once tests finish.
See .env.example for the full list of variables the app expects, with placeholder values and guidance for each.