**
Part 1: Backend Foundation creating a Ticket raising platform
**
A practical guide to creating real-world backend systems using Mongoose, Express middleware, and secure authentication.
Introduction
Customer support systems are an essential part of any tech-driven business, and building one from scratch is a great way to learn full-stack development with real-world requirements.
In this series, I'm documenting the development of HelpMe, an AI-powered support ticket system built using the MERN stack (MongoDB, Express, React, Node.js). The goal is to simulate a production-grade ticketing platform with:
- Role-based access (user, agent, admin)
- JWT-based authentication
- Ticket lifecycle management
- Modular, scalable backend structure
- AI features (coming soon)
This post will cover the backend foundation — ideal for anyone looking to build a professional-grade project using Express and MongoDB.
Project Structure
Organizing files in a clean, modular way is key to scaling your backend efficiently. Here's how the backend is structured:
Environment Setup
- Initialize the project
npm init -y
npm install express mongoose dotenv cookie-parser bcryptjs jsonwebtoken
- Create the entry file
// server.js
import express from 'express';
import mongoose from 'mongoose';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
dotenv.config();
const app = express();
app.use(express.json());
app.use(cookieParser());
// Routes (example)
import authRoutes from './routes/auth.js';
app.use('/api/auth', authRoutes);
// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI)
.then(() => app.listen(5000, () => console.log('Server running')))
.catch(err => console.error('MongoDB connection failed:', err));
Defining the Schemas
- User Schema // models/User.js
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
username: { type: String, required: true, trim: true, unique: true },
email: { type: String, required: true, trim: true, lowercase: true, unique: true },
password: { type: String, required: true, minlength: 6 },
role: { type: String, enum: ['user', 'admin', 'agent'], default: 'user' }
});
export default mongoose.model('User', userSchema);
- Ticket Schema // models/Ticket.js
import mongoose from 'mongoose';
const ticketSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String },
priority: { type: String, enum: ['low', 'medium', 'high'], default: 'low' },
status: { type: String, enum: ['open', 'in-progress', 'closed'], default: 'open' },
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
assignedTo: { type: mongoose.Schema.Types.ObjectId, ref: 'User', default: null }
});
export default mongoose.model('Ticket', ticketSchema);
Why Schema Design Matters
Relationships like createdBy and assignedTo are handled using MongoDB references (ObjectId), enabling efficient population and querying.
Enumerations ensure valid values for priority, status, and role, enforcing business rules at the DB level.
Authentication System
Register, Login & Logout
- Auth routes include: POST /register – Create a user account
POST /login – Authenticate and store JWT in an HTTP-only cookie
POST /logout – Clear the authentication cookie
// routes/auth.js
import express from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import User from '../models/User.js';
const router = express.Router();
router.post('/register', async (req, res) => {
const { username, email, password } = req.body;
const hashed = await bcrypt.hash(password, 10);
const user = new User({ username, email, password: hashed });
await user.save();
res.status(201).json({ message: 'Registration successful' });
});
router.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET);
res.cookie('token', token, { httpOnly: true });
res.status(200).json({ message: 'Login successful' });
});
router.post('/logout', (req, res) => {
res.clearCookie('token');
res.status(200).json({ message: 'Logged out' });
});
export default router;
Middleware: Secure Access
- To protect routes and limit access based on roles, we add two middlewares:
Verify JWT
// middleware/verifyToken.js
import jwt from 'jsonwebtoken';
export const verifyToken = (req, res, next) => {
const token = req.cookies.token;
if (!token) return res.status(401).json({ message: 'Unauthorized' });
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) return res.status(403).json({ message: 'Invalid token' });
req.user = decoded;
next();
});
};
Role-Based Access
// middleware/roleMiddleware.js
export const roleMiddleware = (roles) => (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: 'Access denied' });
}
next();
};
This system allows you to protect endpoints like:
app.get('/tickets/all', verifyToken, roleMiddleware(['admin']), controller);
What’s Covered So Far
Project structure & setup ✅ Done
User & Ticket schemas ✅ Done
Auth routes (/register
, /login
, /logout
) ✅ Done
JWT & role-based middleware ✅ Done
Coming in Part 2
Next, I’ll cover:
- Ticket creation and viewing routes
- Admin: Assign ticket to agent
- Agent dashboard: View assigned tickets
- Threaded replies using a conversation schema
- Integrating AI models for tagging & sentiment
Final Notes
If you're looking to build a full-featured backend using modern Node.js practices — especially for multi-role apps — this architecture is production-ready and extendable.
Feel free to explore, clone, or contribute to the project below.
🔗 GitHub Repo: http://github.com/Abhijeet002/HelpMe-AI-Powered-Support-Ticket-System
Follow me on Dev.to or LinkedIn for the next parts of this series.
Got questions or feedback? Let me know in the comments — happy to help!
Top comments (0)