A collaborative backend platform for software teams to report bugs, suggest features, and coordinate resolutions β built with Node.js, TypeScript, Express.js, and PostgreSQL.
https://dev-pluse-five.vercel.app/
- π JWT Authentication β Secure signup & login with token-based auth
- π₯ Role-Based Access Control β
contributorandmaintainerroles with distinct permissions - π Issue Management β Create, read, update, and delete bug reports & feature requests
- π Filtering & Sorting β Filter issues by
type,status; sort bynewestoroldest - π Password Security β bcrypt hashing (salt rounds: 10)
- π§± Modular Architecture β Clean separation of concerns with modules, middleware, config, and utils
- π« No ORM / No SQL JOIN β Raw SQL with
pool.query()only - β Consistent API Responses β Standardized success/error response structure throughout
| Technology | Purpose |
|---|---|
| Node.js (LTS) | Runtime environment |
| TypeScript | Type-safe development |
| Express.js | Web framework (modular router) |
| PostgreSQL | Relational database (NeonDB) |
pg (node-postgres) |
Native PostgreSQL driver |
| bcrypt | Password hashing |
| jsonwebtoken | JWT generation & verification |
| dotenv | Environment variable management |
src/
βββ app.ts # Express app setup
βββ server.ts # Server entry point
βββ errors/ # Async error wrapper
β βββ AppError.ts # Prepare error message with status code
βββ config/
β βββ schema.ts # PostgreSQL database create with pool configuration
βββ middleware/
β βββ auth.ts # JWT verification & Role-based authorization
β βββ globalErrorHandler.ts # Standardized error response formatter
β βββ index.d.ts # it's use for help to set JWT payload card on `Request`
βββ modules/
β βββ auth/
β β βββ auth.routes.ts
β β βββ auth.controller.ts
β β βββ auth.service.ts
β |ββ issues/
β | βββ issues.routes.ts
β | βββ issues.controller.ts
β | βββ issues.service.ts
β | βββ issues.interface.ts
β βββ users/
β βββ users.routes.ts
β βββ users.controller.ts
β βββ users.service.ts
β βββ users.interface.ts
βββ types/
βββ index.ts
βββ utils/
βββ sendResponse.ts # Standardized response formatter
βββ jwt.ts
| Column | Type | Constraints |
|---|---|---|
id |
SERIAL |
PRIMARY KEY |
name |
VARCHAR(20) |
NOT NULL |
email |
VARCHAR(50) |
UNIQUE, NOT NULL |
password |
TEXT |
NOT NULL (bcrypt hashed) |
role |
VARCHAR(15) |
DEFAULT 'contributor', NOT NULL |
created_at |
TIMESTAMP |
DEFAULT CURRENT_TIMESTAMP |
updated_at |
TIMESTAMP |
DEFAULT CURRENT_TIMESTAMP |
| Column | Type | Constraints |
|---|---|---|
id |
SERIAL |
PRIMARY KEY |
title |
VARCHAR(150) |
NOT NULL |
description |
TEXT |
NOT NULL (min 20 chars) |
type |
VARCHAR(20) |
'bug', 'feature_request' or 'resolved' |
status |
VARCHAR(15) |
DEFAULT 'open' |
reporter_id |
INTEGER |
NOT NULL (validated in app logic) |
created_at |
TIMESTAMP |
DEFAULT CURRENT_TIMESTAMP |
updated_at |
TIMESTAMP |
DEFAULT CURRENT_TIMESTAMP |
- Node.js v24.x or higher
- PostgreSQL running locally
npm
git clone https://github.com/PrantaBaruaDev/PH-B7-L2-Assignment-2.git
cd PH-B7-L2-Assignment-2npm installnpm i -g typescript
npm i tsx
npm i express
npm i tsup // for build project
npm i dotenv
npm i pg
npm i bcryptjs
npm i jsonwebtoken Create a .env file in the root directory:
PORT=5000
DATABASE_URL=postgresql://username:password@localhost:5432/devpulse
JWT_SECRET=your_super_secret_jwt_key
JWT_REFRESH_SECRET=your_super_secret_jwt_key
NODE_ENV=developmentRun the following SQL in your PostgreSQL client (psql / pgAdmin):
CREATE TABLE IF NOT EXISTS users(
id SERIAL PRIMARY KEY,
name VARCHAR(20) NOT NULL,
email VARCHAR(50) UNIQUE NOT NULL,
password TEXT NOT NULL,
role VARCHAR(15) CHECK (role IN ('contributor', 'maintainer')) DEFAULT 'contributor' NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS issues (
id SERIAL PRIMARY KEY,
title VARCHAR(150) NOT NULL,
description TEXT NOT NULL CHECK (char_length(description) >= 20),
type VARCHAR(20) CHECK (type IN ('bug', 'feature_request')) NOT NULL,
status VARCHAR(15) CHECK (status IN ('open', 'in_progress', 'resolved')) DEFAULT 'open' NOT NULL,
reporter_id INT NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);npm run devServer starts at: http://localhost:5000
npm run build
npm starthttps://dev-pluse-five.vercel.app/api
| Method | Endpoint | Access | Description |
|---|---|---|---|
| POST | /auth/signup |
Public | Register a new user |
| POST | /auth/login |
Public | Login and receive JWT |
| Method | Endpoint | Access | Description |
|---|---|---|---|
| POST | /issues |
Authenticated | Create a new issue |
| GET | /issues |
Public | Get all issues (with filters & sorting) |
| GET | /issues/:id |
Public | Get a single issue by ID |
| PATCH | /issues/:id |
Maintainer / Contributor (own, status = open) | Update an issue |
| DELETE | /issues/:id |
Maintainer only | Delete an issue |
| Param | Values | Default |
|---|---|---|
sort |
newest, oldest |
newest |
type |
bug, feature_request |
(none) |
status |
open, in_progress, resolved |
(none) |
Examples:
GET /api/issues?sort=newest
GET /api/issues?type=bug
GET /api/issues?status=open
GET /api/issues?type=bug&status=open&sort=oldest
Response (200)
{
"success": true,
"data": [
{
"id": 6,
"title": "Database connection timeout under load",
"description": "Updated description with reproduction steps...",
"type": "feature_request",
"status": "open",
"reporter": {
"id": 2,
"name": "John Maintainer Doe",
"role": "maintainer"
},
"created_at": "2026-05-23T12:49:33.061Z",
"updated_at": "2026-05-23T12:49:33.061Z"
},
{
"id": 3,
"title": "john - Database Emargency problem pool exhaustion fix needed",
"description": "Updated description with reproduction steps...",
"type": "feature_request",
"status": "open",
"reporter": {
"id": 1,
"name": "John Doe",
"role": "contributor"
},
"created_at": "2026-05-23T12:08:51.359Z",
"updated_at": "2026-05-23T13:09:25.917Z"
}
]
}Response (200)
{
"success": true,
"message": "Issue retrived successfully",
"data": {
"id": 45,
"title": "Database connection timeout under load",
"description": "Pool exhausts after 50+ concurrent queries, causing 500 errors",
"type": "bug",
"status": "open",
"reporter": {
"id": 1,
"name": "John Doe",
"role": "contributor"
},
"created_at": "2026-01-20T10:30:00Z",
"updated_at": "2026-01-20T14:45:00Z"
}
}Request:
{
"name": "John Doe",
"email": "john@devpulse.com",
"password": "securePass123",
"role": "contributor"
}Response (201):
{
"success": true,
"message": "User registered successfully",
"data": {
"id": 1,
"name": "John Doe",
"email": "john@devpulse.com",
"role": "contributor",
"created_at": "2026-05-22T10:00:00Z",
"updated_at": "2026-05-22T10:00:00Z"
}
}Request:
{
"email": "john@devpulse.com",
"password": "securePass123"
}Response (200):
{
"success": true,
"message": "Login successful",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"name": "John Doe",
"email": "john@devpulse.com",
"role": "contributor",
"created_at": "2026-01-20T09:00:00Z",
"updated_at": "2026-01-20T09:00:00Z"
}
}
}Headers: Authorization: <JWT_TOKEN>
Request:
{
"title": "Database connection timeout under load",
"description": "Pool exhausts after 50+ concurrent queries, causing 500 errors",
"type": "bug"
}Response (201):
{
"success": true,
"message": "Issue created successfully",
"data": {
"id": 45,
"title": "Database connection timeout under load",
"description": "Pool exhausts after 50+ concurrent queries, causing 500 errors",
"type": "bug",
"status": "open",
"reporter_id": 1,
"created_at": "2026-05-22T10:30:00Z",
"updated_at": "2026-05-22T10:30:00Z"
}
}Headers: Authorization: <JWT_TOKEN>
Request:
{
"title": "Updated: Database pool exhaustion fix needed",
"description": "Updated description with reproduction steps...",
"type": "bug"
}Response (201):
{
"success": true,
"message": "Issue updated successfully",
"data": {
"id": 45,
"title": "Updated: Database pool exhaustion fix needed",
"description": "Updated description with reproduction steps...",
"type": "bug",
"status": "open",
"reporter_id": 1,
"created_at": "2026-05-22T10:30:00Z",
"updated_at": "2026-05-22T10:30:00Z"
}
}Headers: Authorization: <JWT_TOKEN>
Response (200):
{
"success": true,
"message": "Issue deleted successfully"
}| Action | contributor |
maintainer |
|---|---|---|
| Register / Login | β | β |
| Create issue | β | β |
| View all issues | β | β |
| Update own issue (open) | β | β |
| Update any issue | β | β |
| Delete issue | β | β |
| Change issue status | β | β |
All Success responses follow this structure:
{
"success": true,
"message": "Operation description",
"data": "Response data"
}All error responses follow this structure:
{
"success": false,
"message": "Error description",
"errors": "Detailed error information" // (Only Development Environment)
}| Status Code | Meaning |
|---|---|
200 |
Good Response OK - Successful GET, PATCH, PUT, DELETE |
201 |
Create Data - Created Successful POST (resource created) |
204 |
No Content - Successful DELETE with no response body |
400 |
Bad Request β validation error, duplicate resource |
401 |
Unauthorized β missing, expired, or invalid JWT |
403 |
Forbidden β valid token but insufficient role/permissions |
404 |
Not Found β resource does not exist |
409 |
Conflict β editing a non-open issue as contributor |
500 |
Internal Server Error β unexpected server/database error |
Batch: L2B7 β Programming Hero
Built with β€οΈ for the Programming Hero Level 2 β Batch 7 Assignment