A fully-featured RESTful API with three independent authentication contexts, bulk Excel import, image management, a real-time enquiry system, and security best practices baked in from day one.
- Overview
- Features
- Tech Stack
- Architecture
- Project Structure
- Getting Started
- Environment Variables
- API Reference
- Authentication Flow
- Bulk Import Guide
- Database Schema
- Running in Production
- Contributing
This project is a scalable backend API for a product catalog platform - vehicle parts, SKUs, and inventory - designed to serve three distinct user roles: Admins, End Users, and Internal Staff.
Key design principles followed throughout:
- β Separation of concerns - Controllers β Services β Models (no business logic in routes)
- β Role-isolated authentication - Three separate JWT secrets and middleware stacks
- β Fail-safe data ingestion - Excel files are validated before import, not after
- β Production-ready logging - Winston for structured logs, Morgan for HTTP request trails
- β Defensive security - Helmet headers, CORS, rate limiting, bcrypt, Joi validation on every endpoint
|
π Authentication & Security
|
π¦ Product Catalog
|
|
π₯ Bulk Data Operations
|
π₯ User-Facing Features
|
| Category | Technology | Purpose |
|---|---|---|
| Runtime | Node.js 18+ | JavaScript server runtime |
| Framework | Express.js v5 | HTTP server & routing |
| Database | MySQL 8 | Relational data storage |
| ORM | Sequelize v6 | Database modelling & queries |
| Auth | jsonwebtoken | Access & refresh token signing |
| Security | bcryptjs | Password hashing |
| Validation | Joi | Request body & param schemas |
| File Uploads | Multer v2 | Multipart form handling |
| Excel | ExcelJS | Bulk import / export |
| Nodemailer | OTP & transactional email | |
| Logging | Winston + Morgan | Structured logs + HTTP access logs |
| Security Headers | Helmet | HTTP response header hardening |
| Rate Limiting | rate-limit-express | Brute-force protection |
| Dev Server | Nodemon | Hot reload during development |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client (HTTP Request) β
βββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β Express.js App β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Global Middleware Stack β β
β β cookie-parser β helmet β morgan β cors β β β
β β body-parser β rate-limiter β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β
β β /admin β β /user β β/internal β β /file β β
β β Routes β β Routes β β Routes β β Routes β β
β ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ β
β β β β β β
β ββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββΌββββββ β
β β Auth / Role Middleware (JWT Verify) β β
β ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββ β
β β Controllers (Request β Response) β β
β ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββ β
β β Services (Business Logic Layer) β β
β ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββ β
β β Sequelize Models (ORM Layer) β β
β ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β MySQL Database β
β (Connection Pool: max 5 connections) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
src/
βββ app.js # Express setup, middleware stack, route mounting
βββ server.js # Entry point - DB sync + server start
β
βββ config/
β βββ db.js # Sequelize + MySQL connection pool + SSL support
β βββ jwt.js # JWT config constants
β βββ logger.js # Winston logger (file + console transports)
β βββ multer.js # Multer disk storage configuration
β
βββ models/ # Sequelize model definitions
β βββ index.js # Associations (hasMany, belongsTo, etc.)
β βββ userModel.js
β βββ adminModel.js
β βββ productModel.js # FULLTEXT indexed on keyword + ref_code
β βββ categoryModel.js
β βββ makeModel.js
β βββ enquiryModel.js
β βββ favoriteProductModel.js
β βββ fileModel.js
β βββ refreshToken.js
β βββ otpModel.js
β
βββ controllers/ # Thin layer - validates input, calls service, returns response
β βββ admin/
β β βββ authController.js
β β βββ dashboardController.js
β β βββ userController.js
β β βββ productController.js
β β βββ categoryController.js
β β βββ makeController.js
β β βββ bulkImportController.js
β β βββ imageImportController.js
β β βββ adminEnquiryController.js
β βββ userController.js
β βββ internalUserController.js
β βββ favoriteProductController.js
β βββ userEnquiryController.js
β βββ fileController.js
β
βββ services/ # All business logic lives here
β βββ adminService.js
β βββ userService.js
β βββ productService.js
β βββ categoryService.js
β βββ makeService.js
β βββ enquiryService.js
β βββ favoriteProductService.js
β βββ bulkImportService.js # Excel parsing, row validation, DB insertion
β βββ imageImportService.js # Image matching, bulk upload, report generation
β βββ dashboardService.js
β βββ emailService.js # Nodemailer OTP dispatch
β βββ fileService.js
β
βββ routes/
β βββ admin/
β β βββ index.js # Mounts auth, dashboard, users, catalog, enquiry
β β βββ auth.js
β β βββ dashboard.js
β β βββ user.js
β β βββ product.js # Makes, Categories, Products, Bulk Import, Images
β β βββ adminEnquiryRoutes.js
β βββ userRoutes.js
β βββ internalUserRoutes.js
β βββ favoriteRoutes.js
β βββ userEnquiryRoutes.js
β βββ fileRoutes.js
β
βββ middlewares/
β βββ authMiddleware.js # Verifies user JWT (cookie + Bearer header fallback)
β βββ adminMiddleware.js # Verifies admin JWT
β βββ internalAuthMiddleware.js # Verifies internal user JWT
β βββ roleMiddleware.js # Role-based access control
β βββ isAdminOrUser.js # Shared admin+user route guard
β βββ validateMiddleware.js # Joi schema runner
β βββ errorMiddleware.js # Global error handler (4xx / 5xx)
β
βββ validators/ # Joi schemas - one file per domain
β βββ productSchemas.js
β βββ userProperty.js
β βββ admin.js
β βββ favoriteSchemas.js
β βββ enquirySchemas.js
β
βββ utils/
βββ apiResponse.js # successResponse() / errorResponse() helpers
βββ slug.js # slugify wrapper
βββ validation.js # Shared validation helpers
- Node.js v18+
- MySQL v8+
- npm v9+
# 1. Clone the repository
git clone https://github.com/your-username/product-catalog-api.git
cd product-catalog-api
# 2. Install dependencies
npm install
# 3. Set up environment variables
cp .env.example .env
# Open .env and fill in your values (see section below)
# 4. Start the development server
npm run devThe server starts on http://localhost:5000 by default.
Create a .env file at the root of the project:
# ββ Server ββββββββββββββββββββββββββββββββ
PORT=5000
NODE_ENV=development # development | production
# ββ Database ββββββββββββββββββββββββββββββ
DB_HOST=localhost
DB_PORT=3306
DB_NAME=product_catalog_db
DB_USER=root
DB_PASSWORD=your_password
DB_SSL=false # Set to true for cloud databases
# ββ JWT Secrets (keep these long & random) ββ
JWT_USER_SECRET=your_user_jwt_secret_key
JWT_ADMIN_SECRET=your_admin_jwt_secret_key
JWT_INTERNAL_SECRET=your_internal_jwt_secret_key
# ββ Token Expiry ββββββββββββββββββββββββββ
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
# ββ Email (Nodemailer) ββββββββββββββββββββ
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_app_password
# ββ Sync DB (development only) ββββββββββββ
SHOULD_SYNC=false # Set to true to run sequelize.sync({ alter: true })
β οΈ Never commit your.envfile. Add it to.gitignore.
Base URL: http://localhost:5000/api
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/admin/auth/login |
Admin login β returns access + refresh token | - |
POST |
/admin/auth/logout |
Invalidate refresh token | π |
GET |
/admin/auth/me |
Get current admin profile | π |
POST |
/admin/auth/change-password |
Update admin password | π |
POST |
/admin/auth/refresh-token |
Rotate access token | - |
POST |
/admin/auth/create |
Create new admin account | - |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/admin/dashboard |
Aggregated platform statistics | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/admin/users |
List all users (paginated) | π |
GET |
/admin/users/:id |
Get single user | π |
POST |
/admin/users |
Create user | π |
PUT |
/admin/users/:id |
Update user | π |
DELETE |
/admin/users/:id |
Delete user | π |
DELETE |
/admin/users/bulk |
Bulk delete users | π |
PATCH |
/admin/users |
Bulk update users | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/admin/catalog/makes |
Paginated list with search | π |
GET |
/admin/catalog/makes/active |
Active makes (for dropdowns) | π |
GET |
/admin/catalog/makes/:id |
Get by ID | π |
POST |
/admin/catalog/makes |
Create | π |
PUT |
/admin/catalog/makes/:id |
Update | π |
DELETE |
/admin/catalog/makes/:id |
Delete | π |
POST |
/admin/catalog/makes/bulk-delete |
Bulk delete | π |
POST |
/admin/catalog/makes/bulk-update-status |
Bulk toggle status | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/admin/catalog/categories |
Paginated list with search | π |
GET |
/admin/catalog/categories/active |
Active categories (for dropdowns) | π |
GET |
/admin/catalog/categories/:id |
Get by ID | π |
POST |
/admin/catalog/categories |
Create | π |
PUT |
/admin/catalog/categories/:id |
Update | π |
DELETE |
/admin/catalog/categories/:id |
Delete | π |
POST |
/admin/catalog/categories/bulk-delete |
Bulk delete | π |
POST |
/admin/catalog/categories/bulk-update-status |
Bulk toggle status | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/admin/catalog/products |
Paginated list + filters + FULLTEXT search | π |
GET |
/admin/catalog/products/:id |
Full product detail with images | π |
GET |
/admin/catalog/products/ref/:refCode |
Products by reference code | π |
POST |
/admin/catalog/products |
Create product | π |
PUT |
/admin/catalog/products/:id |
Update product | π |
DELETE |
/admin/catalog/products/:id |
Delete product | π |
POST |
/admin/catalog/products/bulk-delete |
Bulk delete | π |
POST |
/admin/catalog/products/bulk-update-status |
Bulk toggle status | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/admin/catalog/import/template |
Download Excel template | - |
POST |
/admin/catalog/import/validate |
Validate file (no DB write) | π |
POST |
/admin/catalog/import/products |
Bulk import products from Excel | π |
POST |
/admin/catalog/import/keywords |
Bulk import keywords | π |
POST |
/admin/catalog/images/check-missing |
Report missing product images | π |
POST |
/admin/catalog/images/bulk-upload |
Bulk upload images for all products | π |
POST |
/admin/catalog/images/upload-specific |
Upload images for specific codes | π |
GET |
/admin/catalog/images/missing-report |
Download missing images report (Excel) | - |
GET |
/admin/catalog/images/product-image-report |
Full product image coverage report | - |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/admin/enquiry/stats |
Enquiry statistics summary | π |
GET |
/admin/enquiry |
All enquiries with filters | π |
GET |
/admin/enquiry/:enquiryId |
Single enquiry detail | π |
PUT |
/admin/enquiry/:enquiryId |
Update status / priority / remarks | π |
DELETE |
/admin/enquiry/:enquiryId |
Delete enquiry | π |
DELETE |
/admin/enquiry/bulk |
Bulk delete enquiries | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/user/register |
Register new account | - |
POST |
/user/login |
Login β tokens | - |
POST |
/user/logout |
Logout | - |
POST |
/user/refresh-token |
Rotate access token | - |
POST |
/user/forgot-password |
Request OTP via email | - |
POST |
/user/verify-otp |
Verify OTP code | - |
POST |
/user/reset-password |
Set new password | - |
GET |
/user/profile |
Get my profile | π |
PUT |
/user/profile |
Update my profile | π |
GET |
/user/property |
Get user properties | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/user/favoriteproduct |
Add product to favorites | π |
GET |
/user/favoriteproduct |
List favorites (paginated) | π |
GET |
/user/favoriteproduct/count |
Get favorites count | π |
GET |
/user/favoriteproduct/check/:productId |
Check if favorited | π |
DELETE |
/user/favoriteproduct/:productId |
Remove from favorites | π |
POST |
/user/favoriteproduct/bulk-remove |
Bulk remove favorites | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/user/enquiry |
Submit a new enquiry | π |
GET |
/user/enquiry |
My enquiry history | π |
GET |
/user/enquiry/:enquiryId |
Single enquiry detail | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/internal/login |
Internal staff login | - |
POST |
/internal/logout |
Logout | - |
POST |
/internal/refresh-token |
Rotate access token | - |
POST |
/internal/forgot-password |
Request OTP | - |
POST |
/internal/verify-otp |
Verify OTP | - |
POST |
/internal/reset-password |
Reset password | - |
GET |
/internal/profile |
Get profile | π |
PUT |
/internal/profile |
Update profile | π |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/file/upload/single/:ownerType |
Single file upload | π |
POST |
/file/upload/multiple/:ownerType |
Up to 10 files | π |
π = Requires
Authorization: Bearer <token>header or*_tokencookie
Three fully isolated auth contexts - each has its own JWT secret, middleware, and token model:
βββββββββββββββββββββββββββββββ
β Login β
β POST /[role]/auth/login β
ββββββββββββ¬βββββββββββββββββββ
β
βββββββββββββββββββββΌββββββββββββββββββββ
β Response: β
β β’ accessToken (15 min, in body) β
β β’ refreshToken (7 days, HttpOnly πͺ) β
βββββββββββββββββββββ¬ββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
β Protected Request β
β Authorization: Bearer <accessToken> β
β - or - Cookie: user_token=<accessToken> β
ββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ
β
ββββββββββββββΌβββββββββββββ
β Token Expired? β
ββββββββββββββ¬βββββββββββββ
Yes β No
ββββββββββββββΌβββββββ βββββββββββββββββ
β POST /refresh-tokenβ β Request passes β
β β New access token β βββββββββββββββββ
ββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββ
β Forgot Password Flow β
β 1. POST /forgot-password β OTP email β
β 2. POST /verify-otp β validated β
β 3. POST /reset-password β new pass β
ββββββββββββββββββββββββββββββββββββββββ
- Download the Excel template:
GET /api/admin/catalog/import/template - Fill it in following this column spec:
| Column | Required | Type | Notes |
|---|---|---|---|
product_code |
β | String | Unique part number (primary key) |
name |
β | String | Product display name |
make_id |
β | Integer | Must match existing Make ID |
category_id |
β | Integer | Must match existing Category ID |
mrp |
β | Decimal | Maximum retail price |
color |
β | String | Colour description |
keyword |
β | String | Comma-separated search keywords |
ref_code |
β | String | Comma-separated reference codes |
status |
β | Enum | active / inactive / out_of_stock |
- Validate first (no DB writes):
POST /api/admin/catalog/import/validate- Returns row-level error messages if validation fails
- Import:
POST /api/admin/catalog/import/products- Max file size: 10MB
- Accepted formats:
.xlsx,.xls
sk_admin βββββββββββββββββββββββββββββββββββββββββββ
sk_user βββββββ¬βββββββββββββββββββββββββββββββββββ β
β β β
βΌ βΌ βΌ
sk_favorite_product sk_product βββ sk_make
sk_enquiry β sk_category
sk_refresh_token sk_file
sk_otp
| Table | Description |
|---|---|
sk_user |
End user accounts |
sk_admin |
Admin accounts |
sk_product |
Products (FULLTEXT indexed on keyword, ref_code) |
sk_category |
Product categories |
sk_make |
Vehicle/brand makes |
sk_enquiry |
Customer enquiries |
sk_favorite_product |
User β Product favorites junction |
sk_file |
Uploaded file metadata (path, type, owner) |
sk_refresh_token |
Per-role refresh token storage |
sk_otp |
Time-limited OTPs for password reset |
# Set environment
NODE_ENV=production npm startRecommended: Use PM2 for process management
npm install -g pm2
# Start
pm2 start src/server.js --name "catalog-api"
# Auto-restart on crash & server reboot
pm2 save
pm2 startup
# Monitor
pm2 monitChecklist before going live:
- Set all
.envsecrets to strong random values - Set
DB_SSL=trueif using a cloud database - Set
SHOULD_SYNC=falsein production - Configure allowed origins in
corsOptions - Set up a reverse proxy (Nginx / Caddy) in front of Express
- Enable HTTPS (Let's Encrypt / Cloudflare)
Contributions are welcome!
# 1. Fork the repo & create your branch
git checkout -b feature/your-feature-name
# 2. Commit with conventional commits
git commit -m "feat: add your feature"
# 3. Push and open a PR
git push origin feature/your-feature-nameDistributed under the ISC License. See LICENSE for more information.
Made with β€οΈ and Node.js