SupportDesk is a support ticket management platform built to explore role-based access control, RESTful API design, and containerized full-stack infrastructure with Docker.
The server is built using Node.js + Express, communicates with a MongoDB database, and exposes a typed REST API consumed by a React frontend. The application supports two roles, clients (who open tickets) and agents (who manage and resolve them), with strict permission enforcement across all endpoints.
Backend
- Node.js + Express 5
- MongoDB 7 + Mongoose 9
- JSON Web Tokens (JWT) for authentication
- Bcrypt for password hashing
- Zod for request validation
- Jest + Supertest for integration testing
- TypeScript + ESM
Frontend
- React 19 + TypeScript
- Vite 8
- React Router v7
- TanStack React Query v5
- Styled Components v6
- Axios
- Lucide React (icons)
Infrastructure
- Docker + Docker Compose
- Nginx (reverse proxy)
The backend follows a layered architecture:
Routes β Controllers β Services β Models
- Global error handling via
AppError+errorMiddleware - MongoDB connection via Mongoose
- JWT authentication via
authenticatemiddleware - Role enforcement via
requireRolemiddleware
The frontend is structured around React contexts (AuthContext, ThemeContext, ToastContext) with server state managed by TanStack React Query and routing handled by React Router with protected and public route guards.
All services are orchestrated with Docker Compose:
services:
mongo:
image: mongo:7
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
environment:
MONGO_INITDB_DATABASE: supportdesk
backend:
build: ./backend
expose:
- "3000"
env_file: ./backend/.env
depends_on:
- mongo
frontend:
build: ./frontend
expose:
- "5173"
depends_on:
- backend
environment:
- BACKEND_URL=http://backend:3000
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- backend
- frontend
volumes:
mongo_data:Nginx listens on port 80 and proxies /api/ requests to the backend on port 3000 and all other requests to the frontend dev server on port 5173 (with WebSocket upgrade support for Vite HMR).
Tickets follow a strict state machine with role-based transitions:
| Status | Description |
|---|---|
open |
Ticket created by a client, awaiting an agent |
in_progress |
An agent has assigned themselves to the ticket |
closed |
The assigned agent has resolved the ticket |
canceled |
The client who created the ticket has canceled it |
State transitions:
| Action | Endpoint | Allowed Role | Condition |
|---|---|---|---|
| Create | POST /tickets |
client | β |
| Assign | PATCH /tickets/:id/assign |
agent | Ticket must be open |
| Unassign | PATCH /tickets/:id/unassign |
agent | Must be the assigned agent |
| Close | PATCH /tickets/:id/close |
agent | Must be the assigned agent |
| Cancel | PATCH /tickets/:id/cancel |
client | Must be the ticket creator |
| Comment | POST /tickets/:id/comments |
client or agent | Ticket must not be closed |
The backend exposes a REST API on port 3000 (proxied via Nginx at /api/).
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/register |
Register a new user |
| POST | /auth/login |
Authenticate and receive a JWT |
All ticket routes require a valid Authorization: Bearer <token> header.
| Method | Endpoint | Role | Description |
|---|---|---|---|
| GET | /tickets |
any | List tickets (filtered by role) |
| POST | /tickets |
client | Create a new ticket |
| GET | /tickets/:id |
any | Get ticket details |
| PATCH | /tickets/:id/assign |
agent | Assign ticket to self |
| PATCH | /tickets/:id/unassign |
agent | Unassign self from ticket |
| PATCH | /tickets/:id/close |
agent | Close an assigned ticket |
| PATCH | /tickets/:id/cancel |
client | Cancel own ticket |
| POST | /tickets/:id/comments |
any | Add a comment to a ticket |
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Returns API and DB status |
| Field | Type | Description |
|---|---|---|
name |
String | Full name |
email |
String | Unique, lowercase |
password |
String | Hashed with bcrypt (min 6 chars) |
role |
Enum | client or agent |
createdAt |
Date | Auto-generated |
Passwords are stripped from all JSON responses.
| Field | Type | Description |
|---|---|---|
title |
String | Short description |
description |
String | Full problem description |
status |
Enum | open, in_progress, closed, canceled |
priority |
Enum | low, medium, high |
createdBy |
ObjectId | Reference to User |
assignedTo |
ObjectId | Reference to User (nullable) |
comments |
Array | Embedded comment subdocuments |
createdAt |
Date | Auto-generated |
| Field | Type | Description |
|---|---|---|
author |
ObjectId | Reference to User |
body |
String | Comment text |
createdAt |
Date | Auto-generated |
The backend has integration tests written with Jest and Supertest, covering authentication and the full ticket lifecycle.
cd backend
npm testTests run against a separate MongoDB instance defined by MONGO_TEST_URL. The database is wiped before each test case to ensure isolation.
Auth tests cover: registration, duplicate email rejection, login, and wrong password rejection.
Ticket tests cover: creation (client/agent roles), listing, ownership checks, assign/unassign/close/cancel flows, comment permissions, and unauthenticated access.
1. Clone the repository
git clone https://github.com/DavidEricson00/SupportDesk.git
cd SupportDesk2. Configure the backend environment
cp backend/.env.example backend/.envFill in the values (see Environment Variables below).
3. Start all services
docker compose up -d --buildThe application will be available at http://localhost.
4. (Optional) Run tests
cd backend
npm testCreate backend/.env based on backend/.env.example:
PORT=
MONGO_URL=
MONGO_TEST_URL=
JWT_SECRET=
NODE_ENV=| Variable | Description |
|---|---|
PORT |
Port for the Express server (default: 3000) |
MONGO_URL |
MongoDB connection string for the main database |
MONGO_TEST_URL |
MongoDB connection string for the test database |
JWT_SECRET |
Secret key used to sign JWT tokens |
NODE_ENV |
Environment (development, production, test) |



