Monorepo containing two main projects: a backend (API + worker) and a frontend (Next.js UI).
The backend implements a production-ready Node.js + TypeScript API for user authentication and image processing. The frontend is a minimal Next.js application that consumes the backend APIs.
- JWT Authentication (Register / Login / Profile)
- Image Uploads using Multer + Cloudinary
- Image Transformations (resize, crop, rotate, format, filters)
- Paginated Image Listing per user
- Background Worker (BullMQ + Redis)
- Prisma ORM with SQLite
- Centralized Error Handling Scalable Architecture (Controller / Service / Model)
frontend/
backend/
├── package.json
├── tsconfig.json
├── prisma/
│ ├── schema.prisma
│ └── migrations/
└── src/
├── app.ts
├── server.ts
├── worker.ts
└── ...
backend/src/app.tssets up routes and middlewarebackend/src/server.tsstarts the HTTP serverbackend/src/worker.tsruns background queue jobs
- Node.js
- TypeScript
- Express
- Prisma ORM
- SQLite
- Cloudinary
- Multer
- JWT
- BullMQ
- Redis
Create a .env file for the backend (example):
DATABASE_URL="file:./dev.db"
JWT_SECRET=your_jwt_secret
CLOUDINARY_CLOUD_NAME=xxxx
CLOUDINARY_API_KEY=xxxx
CLOUDINARY_API_SECRET=xxxx
REDIS_URL=redis://localhost:6379This repository is split into two projects. Install dependencies either per-package or using a workspace-aware package manager (e.g. pnpm).
Per-package (works with npm / yarn):
# Backend
cd backend && npm install
# Frontend
cd ../frontend && npm installOr, if you use pnpm with workspaces from the repository root:
pnpm installcd backend
npx prisma generate
npx prisma migrate dev --name initRun each part separately depending on what you need to work on.
Backend (development):
cd backend
npm run devBackend (production build):
cd backend
npm run build
npm startWorker (background jobs):
cd backend
npm run start_workerFrontend (development):
cd frontend
npm run dev- Frontend URL: http://localhost:3000
- API base URL: http://localhost:5000 (adjust with
NEXT_PUBLIC_API_BASE_URLinfrontend/.env.local)
- Register / Login / Profile (
/users/*) - Image upload (
POST /images) - Upload status (
GET /images/:id/status) - Get image by public ID (
GET /images/:publicId) - Paginated images (
GET /images?page=&limit=) - Transform image (
POST /images/transform)
| Method | Endpoint | Description |
|---|---|---|
| POST | /users/register |
Register user |
| POST | /users/login |
Login |
| GET | /users/profile |
Get profile (JWT required) |
| Method | Endpoint | Description |
|---|---|---|
| POST | /images |
Upload image (JWT + multipart) |
| POST | /images/transform |
Transform image |
| GET | /images?page=1&limit=10 |
Paginated images (JWT) |
| GET | /images/:publicId |
Get image by ID |
- Controllers: HTTP layer only
- Services: Business logic
- Models: Database access (Prisma)
- Utils: Shared helpers
- Middlewares: Auth & validation
All errors go through a centralized error handler:
{
"status": "error",
"message": "Error description"
}