A production-ready backend API for team-based project management. Built with Node.js, TypeScript, Express, Prisma, and PostgreSQL — containerised with Docker and CI/CD powered by GitHub Actions.
| API Base | https://collab-api-k1or.onrender.com |
| Swagger UI | https://collab-api-k1or.onrender.com/api-docs |
| Health Check | https://collab-api-k1or.onrender.com/health |
Test all endpoints interactively via Swagger UI — no Postman needed.
When multiple teams use the same system, they must never see each other's data. This API enforces strict multi-tenant data isolation — every request is scoped to an organisation via JWT. Teams can manage projects, assign tasks, invite members via email, and control access through role-based permissions.
Client Request
│
▼
Rate Limiter (express-rate-limit)
│
▼
Zod Validation (input shape & type check)
│
▼
Auth Middleware (JWT Verification)
│
▼
Controller (handles HTTP request/response)
│
▼
Service (business logic, RBAC)
│
▼
Repository (all DB calls via Prisma ORM)
│
▼
PostgreSQL
Every layer has a single responsibility. Controllers never touch the database. Services never handle HTTP. Repositories never contain business logic.
| Layer | Technology |
|---|---|
| Runtime | Node.js 18 |
| Language | TypeScript |
| Framework | Express.js |
| ORM | Prisma |
| Database | PostgreSQL |
| Auth | JWT + bcrypt |
| Validation | Zod |
| Logging | Winston |
| API Docs | Swagger (OpenAPI 3.0) |
| Nodemailer (Gmail SMTP) | |
| Rate Limiting | express-rate-limit |
| Containerisation | Docker + docker-compose |
| CI/CD | GitHub Actions |
| Testing | Jest + ts-jest |
POST /auth/register— creates user + organisation in a single atomic transactionPOST /auth/login— returns signed JWT
POST /org/invite— OWNER/ADMIN sends email invite with secure tokenPOST /org/accept-invite— invited member registers via tokenGET /org/members— list all members in organisationPUT /org/:id/role— OWNER updates member role (ADMIN or MEMBER only)
POST /projects— create project (OWNER/ADMIN only)GET /projects— list all projects (paginated, org-scoped)GET /projects/:id— get project by ID (org-scoped)
POST /tasks— create task with optional assignee (OWNER/ADMIN only)GET /tasks— list all tasks (paginated, org-scoped, includes assignee details)GET /tasks/:id— get task by IDPUT /tasks/:id— update task title, description, status, or assigneeDELETE /tasks/:id— delete task (OWNER/ADMIN only)
orgId is never trusted from the request body. It is always extracted from the signed JWT payload — preventing users from accessing or modifying data belonging to other organisations.
// orgId always comes from JWT, never from req.body
const orgId = req.user.orgId| Action | OWNER | ADMIN | MEMBER |
|---|---|---|---|
| Create Project | ✅ | ✅ | ❌ |
| Create Task | ✅ | ✅ | ❌ |
| Update Task | ✅ | ✅ | ❌ |
| Delete Task | ✅ | ✅ | ❌ |
| Send Invite | ✅ | ✅ | ❌ |
| Update Member Role | ✅ | ❌ | ❌ |
| View Projects/Tasks | ✅ | ✅ | ✅ |
- Tokens are cryptographically random UUIDs
- Expire after 1 hour
- Single-use — marked as
usedafter acceptance - Email must match the invited address
- Global: 100 requests per 15 minutes per IP
- Auth routes: stricter limit of 10 requests per 15 minutes per IP
Organization
│
├── User (role: OWNER | ADMIN | MEMBER)
│
├── Project (status: ACTIVE | INACTIVE)
│ └── Task (status: TODO | IN_PROGRESS | DONE)
│ ├── createdBy → User
│ └── assignedTo → User (optional)
│
└── InviteToken (token, email, used, expiresAt)
- Docker and docker-compose installed
# Clone the repo
git clone https://github.com/ssiddus/collab-project.git
cd collab-project
# Copy env file and fill in values
cp .env.example .env
# Start everything
docker-compose up --buildThe server runs at http://localhost:9322
Swagger UI: http://localhost:9322/api-docs
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/collab_db
JWT_SECRET=your_jwt_secret_here
EMAIL_USER=your_gmail@gmail.com
EMAIL_PASS=your_gmail_app_password
NODE_ENV=developmentFor
EMAIL_PASS— use a Gmail App Password, not your actual Gmail password. Generate one at: myaccount.google.com/apppasswords
POST /auth/register
{
"name": "John Doe",
"email": "john@company.com",
"password": "password123",
"organisationName": "Acme Corp"
}POST /auth/login
{
"email": "john@company.com",
"password": "password123"
}POST /org/invite
Authorization: Bearer <token>
{
"email": "teammate@company.com"
}An email is sent with an invite token. In development the token is also returned in the response.
POST /projects
Authorization: Bearer <token>
{
"name": "Website Redesign",
"description": "Q2 website overhaul",
"status": "ACTIVE"
}POST /tasks
Authorization: Bearer <token>
{
"title": "Design homepage",
"description": "Create mockups for homepage",
"projectId": "project-uuid-here",
"assignedTo": "user-uuid-here"
}src/
├── controller/ # HTTP request/response handlers
├── services/ # Business logic and RBAC
├── repositories/ # All Prisma DB calls
├── middleware/ # JWT auth, rate limiting, error handling, Zod validation
├── routes/ # Express route definitions with Swagger JSDoc
├── validators/ # Zod schemas for all request bodies
├── types/ # TypeScript interfaces
└── utils/ # JWT, email, Prisma client, Winston logger, Swagger setup
prisma/
├── schema.prisma # Database models
└── migrations/ # Migration history
.github/
└── workflows/
└── ci.yml # GitHub Actions CI pipeline
npx jest29 unit tests across all service layers using Jest and ts-jest. Repositories are fully mocked — no real database needed during test runs.
GitHub Actions runs on every push to main:
Push to main
↓
Checkout code
↓
Install Node.js 18
↓
npm install
↓
npx prisma generate
↓
npm run build (TypeScript compile)
↓
✅ Build passes or ❌ fails with notification
# Install dependencies
npm install
# Run locally (with ts-node-dev)
npm run dev
# Run tests
npx jest
# Build TypeScript
npm run build
# Run migrations
npx prisma migrate dev
# Open Prisma Studio (DB GUI)
npx prisma studioBuilt by Siddu — a backend engineer with production experience in ELK Stack, webMethods ESB integration, and enterprise observability systems.