multi-tenant-task-manager

Multi-Tenant Task Management System (MTMS)

Live demo (GitHub Pages) Docker Compose CI Pages deploy Docker Node React PostgreSQL

Repository: github.com/sakthiii3/multi-tenant-task-manager
Git clone: https://github.com/sakthiii3/multi-tenant-task-manager.git
Author: Sakthi Sundaresan S · Contact: sakthisundaresan17@gmail.com

Features

Tech stack

Layer Technology
Frontend React (Vite), hooks
Backend Node.js, Express
Database PostgreSQL
DevOps Docker Compose

Docker

Three services are defined in docker-compose.yaml:

Service Build / image Host port Purpose
db postgres:16-alpine 5432 PostgreSQL; schema from backend/db/init.sql on first start
api backend/Dockerfile 4000 Node.js + Express REST API
web frontend/Dockerfile + nginx 8080 React static build; nginx proxies /auth, /tasks, /logs, /users, /health to api (same-origin browser calls)

Clone and run:

git clone https://github.com/sakthiii3/multi-tenant-task-manager.git
cd multi-tenant-task-manager
docker compose up --build -d

Useful commands:

docker compose ps
docker compose logs -f api
docker compose down          # stop containers
docker compose down -v       # stop and remove DB volume (fresh schema next up)

Optional: set JWT_SECRET or GOOGLE_CLIENT_ID in your environment before docker compose up (see docker-compose.yaml).

GitHub Pages (static UI only)

Live site: sakthiii3.github.io/multi-tenant-task-manager

GitHub Pages cannot run Node.js, PostgreSQL, or Docker — it only hosts static files. The workflow .github/workflows/github-pages.yml builds the React app and deploys it.

If you only see the README as a “webpage” (no real app), your repo is probably set to Deploy from a branch (e.g. /docs or default branch). Fix it:

  1. GitHub repo → SettingsPages
  2. Under Build and deploymentSource, choose GitHub Actions (not “Deploy from a branch”).
  3. Push to main (or run the Deploy frontend to GitHub Pages workflow manually). The first run may ask you to approve the github-pages environment.

Backend URL (required for login/tasks on the live site):

  1. Deploy the API + database somewhere public (e.g. Render, Fly.io, Railway, or any VPS with Docker).
  2. In the repo: SettingsSecrets and variablesActionsVariables → add:
    • VITE_PUBLIC_API_URL — full base URL of your API, no trailing slash, e.g. https://mtms-api.onrender.com
      (The Pages build injects this as VITE_API_URL so the browser calls your real API.)
  3. Re-run the Deploy frontend to GitHub Pages workflow (or push a commit).

CORS: your API’s CORS_ORIGIN must include https://sakthiii3.github.io (scheme + host only; no path).

Google sign-in: in Google Cloud Console, add Authorized JavaScript origins https://sakthiii3.github.io and set VITE_GOOGLE_CLIENT_ID as an Actions variable if you use it.


Production-style multi-tenant task app: PostgreSQL + Express + React, JWT auth (bcrypt passwords), RBAC (admin / member), activity logging, optional Google sign-in, Docker compose for one-command run.

Demo logins (after loading seed data)

Role Email Password
Admin admin@demo.local DemoPass123!
Member member@demo.local DemoPass123!

Load demo rows (optional):

From the project root, with Postgres running (e.g. docker compose up -d db):

# macOS / Linux / Git Bash
docker compose exec -T db psql -U mtms -d mtms < backend/db/seed.sql
# Windows PowerShell
Get-Content backend/db/seed.sql | docker compose exec -T db psql -U mtms -d mtms

Or with local psql: psql -f backend/db/seed.sql (set DATABASE_URL / connection flags as needed).


1. Folder structure

NITT/
├── docker-compose.yaml       # Postgres + API + static UI (nginx)
├── SYSTEM_DESIGN.md          # Architecture & scaling notes
├── README.md
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   ├── .env.example
│   ├── db/
│   │   ├── init.sql          # Schema (run automatically in Docker on first DB start)
│   │   └── seed.sql          # Optional demo org, users, and tasks
│   └── src/
│       ├── server.js
│       ├── app.js
│       ├── config/db.js
│       ├── middleware/       # auth, validation, errors
│       ├── routes/
│       ├── controllers/
│       ├── services/         # tenant + RBAC query rules
│       └── utils/
└── frontend/
    ├── Dockerfile
    ├── nginx.conf
    ├── vite.config.js
    ├── .env.example
    └── src/
        ├── api/client.js
        ├── context/AuthContext.jsx
        ├── components/
        └── pages/

2. Database schema (summary)

Defined in backend/db/init.sql:

Table Purpose
organizations Tenants
users Users scoped to one org; role enum admin | member; optional Google oauth_provider / oauth_sub
tasks organization_id, title, description, status, created_by, assigned_to
activity_logs organization_id, task_id, action (CREATED / UPDATED / DELETED), performed_by, metadata, created_at

Isolation: every service query includes organization_id from the verified JWT, never from unchecked client input.

RBAC: members see only tasks where created_by = current user; admins see all tasks in the org.


3. API endpoints

Base URL: http://localhost:4000 (or your deployed host).

Auth — /auth

Method Path Auth Description
POST /auth/register No Register: either organizationName (creates org, you become admin) or organizationId (join existing org as member). Body: email, password (≥8), fullName, plus one org field.
POST /auth/login No Body: email, password, optional organizationId if the same email exists in multiple orgs.
GET /auth/me Bearer JWT Current user + organization.
POST /auth/google No Body: credential (Google ID token). Existing users: no extra fields. New users: mode: create + organizationName, or mode: join + organizationId. Requires GOOGLE_CLIENT_ID on server.

Tasks — /tasks

All require Authorization: Bearer <token>.

Method Path Description
GET /tasks List with pagination page, limit and filters status, assigned_to.
GET /tasks/:id Single task (403/404 if outside tenant or RBAC).
POST /tasks Create. Body: title, optional description, status, assigned_to.
PATCH /tasks/:id Partial update.
DELETE /tasks/:id Delete (activity log written first).

Activity logs — /logs

Method Path Description
GET /logs Paginated (page, limit), optional task_id filter. Scoped to org.

Users — /users

Method Path Description
GET /users List users in the same organization (assignee picker / filters).

Health

GET /health { "status": "ok" } — process is up (does not check the database).
GET /health/ready { "status": "ready", "database": true } or 503 with { "status": "not_ready", "database": false, "error": "..." } if Postgres is down or misconfigured.

4. Docker setup

Environment variables (see backend/.env.example):


5. How to run

Option A — One command (Docker)

From the project root (NITT/):

docker compose up --build

After changing nginx or API proxy settings, rebuild the UI image: docker compose up -d --build web.

Override secrets:

set JWT_SECRET=your-long-random-secret
set GOOGLE_CLIENT_ID=your-google-web-client-id.apps.googleusercontent.com
docker compose up --build

(On Unix: export JWT_SECRET=....)

Option B — Local development (hot reload UI)

  1. Start Postgres and create DB, or use Docker only for DB:

    docker compose up db
    
  2. Backend

    cd backend
    cp .env.example .env
    # Edit .env — DATABASE_URL=postgresql://mtms:mtms_secret@localhost:5432/mtms
    npm install
    npm run dev
    
  3. Frontend

    cd frontend
    cp .env.example .env
    # Optional: VITE_GOOGLE_CLIENT_ID for Google button
    npm install
    npm run dev
    

    Vite proxies /auth, /tasks, /logs, /users to localhost:4000, so you can leave VITE_API_URL empty for dev.

First-time data flow

  1. Register → tab New organization → you are admin; note Org # in the header for inviting others.
  2. Second user RegisterJoin with IDmember.
  3. Members only manage tasks they created; admins manage all tasks in the org.

Google OAuth (bonus)

  1. Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client IDsWeb application.
  2. Authorized JavaScript origins: http://localhost:5173 (dev) and/or http://localhost:8080 (Docker UI).
  3. Set the same Client ID in GOOGLE_CLIENT_ID (backend) and VITE_GOOGLE_CLIENT_ID (frontend .env).
  4. Rebuild frontend if using Docker so Vite embeds the ID.

6. Security notes


7. License

Provided as a sample / internship submission; adapt as needed.