Skip to content
BipashaBiPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

TaskFlow — Scalable REST API with Auth & Role-Based Access

A production-shaped REST API with JWT authentication, role-based access control (RBAC), and full CRUD for a task entity, plus a React console to exercise every endpoint.

Built for the Backend Developer (Intern) assignment. Backend is the primary focus; the frontend is a supporting client.


Project View

Demonstration

TaskFlow Demo


Key Features

  • JWT Authentication (Access + Refresh Tokens)
  • Role-Based Access Control (RBAC)
  • Task CRUD Operations
  • Pagination & Filtering
  • Rate Limiting & Security Hardening
  • Swagger/OpenAPI Documentation
  • Dockerized Deployment
  • Centralized Error Handling

Tech stack

Layer Choice
Backend Node.js, Express, MongoDB (Mongoose)
Auth JWT (access + refresh), bcrypt password hashing
Security helmet, CORS, express-rate-limit, express-mongo-sanitize, input validation
Docs Swagger / OpenAPI 3 (/docs) + Postman collection
Frontend React + Vite + React Router
Deploy Dockerfile + docker-compose

Quick start

Option A — Docker (one command, includes MongoDB)

docker compose up --build
# API:  http://localhost:5000
# Docs: http://localhost:5000/docs

Option B — Run locally

Backend

cd backend
cp .env.example .env          # then set a strong JWT_SECRET
npm install
npm run seed                  # creates demo admin + user (optional)
npm run dev                   # http://localhost:5000

Requires a running MongoDB. Either install it locally or run only the DB: docker run -p 27017:27017 mongo:7.

Frontend

cd frontend
npm install
npm run dev                   # http://localhost:5173

The Vite dev server proxies /api to the backend, so no extra config is needed.

Demo accounts (after npm run seed)

Role Email Password
Admin [email protected] Admin@123
User [email protected] User@1234

API overview

Base path: /api/v1. Full interactive docs at /docs; importable spec at /docs.json.

Auth

Method Endpoint Auth Description
POST /auth/register — Register a user (always role user)
POST /auth/login — Get access + refresh tokens
POST /auth/refresh — Exchange a refresh token
GET /auth/me Bearer Current authenticated user

Tasks (Bearer required)

Method Endpoint Description
GET /tasks List own tasks (admins see all); paginated, filterable
POST /tasks Create a task
GET /tasks/:id Get one task
PATCH /tasks/:id Update a task
DELETE /tasks/:id Delete a task

Query params on list: page, limit, status (todo|in_progress|done), priority (low|medium|high).

Admin (Bearer + admin role)

Method Endpoint Description
GET /admin/users List all users
PATCH /admin/users/:id/role Change a user's role

Response shape

Every response uses a consistent envelope:

// success
{ "success": true, "message": "Task created", "data": { /* ... */ } }
// error
{ "success": false, "message": "Validation failed", "errors": [{ "field": "email", "message": "..." }] }

Example

# Register
curl -X POST http://localhost:5000/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"name":"Ada","email":"[email protected]","password":"Passw0rd!"}'

# Create a task (use the accessToken from the response above)
curl -X POST http://localhost:5000/api/v1/tasks \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"title":"Write docs","priority":"high"}'

Project structure

taskflow-api/
├── backend/
│   └── src/
│       ├── config/        # env + db connection
│       ├── models/        # User, Task (Mongoose schemas)
│       ├── controllers/   # request handlers
│       ├── routes/v1/     # versioned routers (auth, tasks, admin)
│       ├── middleware/    # authenticate, authorize, validate, error, rateLimit
│       ├── validators/    # express-validator rule chains
│       ├── utils/         # token, logger, apiResponse, asyncHandler, seed
│       ├── docs/          # swagger spec
│       ├── app.js         # express app assembly
│       └── server.js      # boot + graceful shutdown
├── frontend/              # React + Vite console
├── postman_collection.json
├── docker-compose.yml
├── SCALABILITY.md
└── README.md

Security highlights

  • Passwords hashed with bcrypt (cost 12); never returned in responses (select: false).
  • Short-lived access tokens (15m) + refresh tokens (7d); refresh tokens can't access resources.
  • Users cannot self-assign the admin role at registration.
  • Ownership-scoped queries — a user can never read or mutate another user's tasks.
  • helmet headers, CORS allow-list, mongo-sanitize (NoSQL injection), body-size cap, and rate limiting (stricter on auth routes).
  • Centralized error handler that hides stack traces in production.

See SCALABILITY.md for how this scales to production.

License

MIT


Architecture & Workflows

All diagrams below are Mermaid and render natively on GitHub.

1. System architecture (high level)

graph TB
  subgraph client["Client tier"]
    UI["React + Vite Console<br/>(login · dashboard · admin)"]
  end

  subgraph api["API tier — Express (stateless, horizontally scalable)"]
    direction TB
    MW["Middleware pipeline<br/>helmet · CORS · rate-limit · sanitize · JSON"]
    RT["Versioned router  /api/v1"]
    subgraph routers[" "]
      AR["Auth routes"]
      TR["Task routes"]
      ADR["Admin routes"]
    end
    AUTH["authenticate (JWT)"]
    AUTHZ["authorize (RBAC)"]
    CTRL["Controllers"]
    ERR["Central error handler"]
    DOCS["Swagger /docs"]
  end

  subgraph data["Data tier"]
    MODELS["Mongoose models<br/>User · Task"]
    DB[("MongoDB<br/>indexed + pooled")]
  end

  UI -->|HTTPS + Bearer JWT| MW
  MW --> RT
  RT --> AR & TR & ADR
  AR --> CTRL
  TR --> AUTH --> CTRL
  ADR --> AUTH --> AUTHZ --> CTRL
  CTRL --> MODELS --> DB
  CTRL -. throws .-> ERR
  UI -.reads.-> DOCS
Loading

The API holds no session state — auth is JWT-based, so any instance can serve any request, which is what makes the API tier horizontally scalable.

2. Authentication & token lifecycle

sequenceDiagram
  actor U as User
  participant FE as React Console
  participant API as Express API
  participant DB as MongoDB

  U->>FE: Enter credentials
  FE->>API: POST /auth/login
  API->>DB: find user (+password)
  DB-->>API: user doc
  API->>API: bcrypt.compare(password)
  API-->>FE: 200 { accessToken (15m), refreshToken (7d) }
  FE->>FE: store tokens

  Note over FE,API: Subsequent protected calls
  FE->>API: GET /tasks  (Authorization: Bearer access)
  API->>API: verify access token
  API-->>FE: 200 data

  Note over FE,API: Access token expires
  FE->>API: GET /tasks  (expired access)
  API-->>FE: 401 Token expired
  FE->>API: POST /auth/refresh { refreshToken }
  API-->>FE: 200 { new accessToken }
  FE->>API: retry GET /tasks
  API-->>FE: 200 data
Loading

The console refreshes silently and once per failure, then retries the original request — the user never sees the expiry.

3. Request lifecycle (middleware pipeline)

flowchart LR
  REQ([Incoming request]) --> SEC[helmet / CORS]
  SEC --> RL{Rate limit OK?}
  RL -- no --> R429[429 Too Many Requests]
  RL -- yes --> BODY[Parse + sanitize body]
  BODY --> ROUTE{Route match?}
  ROUTE -- no --> R404[404 Not Found]
  ROUTE -- yes --> AUTHN{Auth required?}
  AUTHN -- yes --> JWT{Valid JWT?}
  JWT -- no --> R401[401 Unauthorized]
  JWT -- yes --> RBAC{Role allowed?}
  AUTHN -- no --> VAL
  RBAC -- no --> R403[403 Forbidden]
  RBAC -- yes --> VAL{Input valid?}
  VAL -- no --> R400[400 + field errors]
  VAL -- yes --> CTRL[Controller -> DB]
  CTRL --> OK[2xx success envelope]
  CTRL -. error .-> EH[Central error handler]
  EH --> ERESP[Consistent error envelope]
Loading

Every failure path produces the same JSON envelope ({ success, message, errors? }), so the client handles all of them uniformly.

4. Role-based access control

flowchart TD
  START([Authenticated request]) --> ROLE{User role?}
  ROLE -- admin --> ALL[Full scope:<br/>all tasks + user management]
  ROLE -- user --> OWN[Scoped to owner == self]
  OWN --> Q[Query filtered by owner: userId]
  ALL --> Q2[Query unfiltered<br/>admin routes gated by authorize#40;admin#41;]
  Q --> RESP([Only own resources returned])
  Q2 --> RESP2([All resources returned])
Loading

Ownership scoping is applied in the query itself, not after fetching — a user can never read or mutate another user's task, even by guessing an ID.

5. Data model

erDiagram
  USER ||--o{ TASK : owns
  USER {
    ObjectId _id
    string name
    string email "unique, indexed"
    string password "bcrypt hash, never returned"
    string role "user | admin"
    date createdAt
    date updatedAt
  }
  TASK {
    ObjectId _id
    string title
    string description
    string status "todo | in_progress | done"
    string priority "low | medium | high"
    date dueDate
    ObjectId owner "FK -> USER, indexed"
    date createdAt
    date updatedAt
  }
Loading

A compound index on { owner, createdAt } backs the dominant query: this user's tasks, newest first.

6. Deployment topology

graph LR
  subgraph internet[Internet]
    BROWSER[Browser]
  end
  subgraph edge[Edge]
    LB[Load balancer / reverse proxy]
  end
  subgraph compute["Compute (autoscaled)"]
    A1[API container 1]
    A2[API container 2]
    AN[API container N]
  end
  subgraph stateful[Stateful]
    REDIS[(Redis<br/>cache + rate limits — next step)]
    MONGO[(MongoDB<br/>replica set)]
  end

  BROWSER --> LB --> A1 & A2 & AN
  A1 & A2 & AN --> MONGO
  A1 & A2 & AN -.planned.-> REDIS
Loading

Because instances are stateless, scaling out is "add another container." docker-compose.yml runs the single-node version of this locally; the dashed Redis layer is the documented next step in SCALABILITY.md.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages