A REST API for file storage with JWT authentication — built to run locally and on serverless.
Boxley is a pet-project backend that combines two things people actually deploy: authentication (JWT access + refresh tokens bound to a device) and file storage (any S3-compatible object store, MinIO out of the box). It ships with Swagger docs, integration tests, and a Vercel-ready configuration.
- 🔐 JWT auth — access + refresh tokens, refresh tokens are bound to a device, expired tokens are cleaned up by a background job
- 📁 File storage — upload, list (paginated), fetch, download, and delete files via an S3-compatible API (MinIO-compatible)
- 🧑 Users — CRUD endpoints guarded by an "owner only" middleware
- 📚 Swagger docs — auto-generated from JSDoc, served at
/api/docs - ✅ Tests — unit + integration (Jest + Supertest) against a real MySQL and MinIO from Docker Compose
- ▲ Serverless-ready — deploys to Vercel as-is (
vercel.jsonincluded)
| Layer | Technology |
|---|---|
| Runtime | Node.js, Express 5, TypeScript |
| Database | MySQL 8 via Prisma 7 (driver adapter @prisma/adapter-mariadb) |
| Storage | Any S3-compatible store via AWS SDK v3 (MinIO for local dev) |
| Validation | Joi + custom validation middleware |
| Testing | Jest 30, Supertest |
| Infra | Docker Compose (MySQL dev + MySQL test + MinIO), Vercel |
- Docker with Docker Compose
- Node.js 20+
cp .env.example .env # then fill in JWT_SECRET and the MySQL credentials
cp .env.test.example .env.test # used by npm testStarts MySQL (dev, port 3306), MySQL (test, port 3307) and MinIO (:9000, console at :9001):
docker-compose up -dnpm run db:pushnpm install # prisma generate runs automatically on postinstall
npm run devThe API is now live at http://localhost:3000 — open the Swagger UI at http://localhost:3000/api/docs.
Configure via a local .env (or project settings on Vercel for deployment).
| Variable | Description |
|---|---|
JWT_SECRET |
Secret used to sign access/refresh tokens |
DATABASE_URL |
MySQL connection string (mysql://user:pass@host:port/db) |
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY |
Credentials for the S3-compatible store |
S3_BUCKET |
Bucket where uploaded files are stored |
S3_ENDPOINT |
Store endpoint (defaults to http://localhost:9000) |
PORT |
Local server port (defaults to 3000) |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/api/auth/signup |
Register a user (id is generated server-side) | — |
POST |
/api/auth/signin |
Log in, get access + refresh tokens | — |
POST |
/api/auth/signin/new_token |
Refresh the access token | — |
GET |
/api/auth/info |
Get current user info | Bearer |
POST |
/api/auth/logout |
Log out on a device | Bearer |
POST |
/api/file/upload |
Upload a file (multipart/form-data) |
Bearer |
GET |
/api/file |
List files (paginated: page, limit) |
Bearer |
GET |
/api/file/:id |
Get file metadata | Bearer |
GET |
/api/file/download/:id |
Download a file | Bearer |
DELETE |
/api/file/:id |
Delete a file (owner only) | Bearer |
POST |
/api/user |
Create a user | — |
GET |
/api/user/:id |
Get user by id (self only) | Bearer |
PUT |
/api/user/:id |
Update user (self only) | Bearer |
DELETE |
/api/user/:id |
Delete user (self only) | Bearer |
Full request/response schemas are in the Swagger UI at
/api/docs.
Tests run against the test MySQL (port 3307) and MinIO from Docker Compose (requires .env.test — see step 0):
npx dotenv -e .env.test -- prisma db push # push schema to the test database
npm test # dotenv -e .env.test -- jest -iThe deployment config already lives in vercel.json: the app is built with @vercel/node from src/index.ts (on Vercel it exports the Express app instead of calling app.listen).
Vercel has no MySQL or MinIO, so wire up managed counterparts:
- MySQL — e.g. PlanetScale or Aiven. (Postgres works too, but you'd need to switch the
providerinprisma/schema.prismaand the adapter to@prisma/adapter-pg.) - S3 — any S3-compatible store: Cloudflare R2 (S3-compatible endpoint in the bucket settings), Backblaze B2, or AWS S3.
- Push the repository to GitHub.
- In Vercel: Add New Project → import the repo, name the project
boxley(gives youboxley.vercel.app). - Under Settings → Environment Variables, set
JWT_SECRET,DATABASE_URL,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY,S3_BUCKET,S3_ENDPOINT. - Push the schema to the managed database from your machine:
DATABASE_URL="<connection string from PlanetScale/Aiven>" npm run db:push - Deploy. 🎉
- node-cron doesn't tick on Vercel: serverless functions are ephemeral, so the in-process refresh-token cleanup job never runs. Options: a Vercel Cron Job hitting an endpoint that calls
deactivateExpiredTokens, or invalidating byexpiresAtwhen a refresh token is used (partially done inrefreshToken). - First requests after idle are slow (serverless cold start).
src/
├── config/ # env config, Swagger setup
├── controllers/ # request handlers
├── middlewares/ # auth, validation, error handling, CORS
├── routes/ # Express routers
├── services/ # business logic (auth, files, users)
├── validations/ # Joi schemas
├── tasks/ # cron jobs (refresh-token cleanup)
└── db/ # Prisma client
tests/
├── integration/ # API tests against real MySQL + MinIO
├── unit/
└── utils/ # test DB setup helpers