SupplyTrack Backend is the Express and MariaDB/MySQL API for the SupplyTrack student-learning project. It teaches how a real ordering system can handle authentication, roles, products, carts, orders, pickup stations, and order tracking history.
This backend is intentionally written in normal JavaScript with clear files and direct SQL queries. The goal is for students to understand the flow before learning heavier abstractions.
- How an Express server is organized.
- How routes connect URLs to controller functions.
- How controllers read request data, validate it, and run database queries.
- How JWT authentication protects private routes.
- How role-based access control keeps users inside their allowed dashboards.
- How bcrypt stores hashed passwords instead of plain text passwords.
- How Multer accepts product image uploads.
- How a cart becomes one or more supplier orders.
- How order status history is stored in an
order_trackingtable. - How to seed a database with realistic test data.
- Node.js
- Express.js
- MariaDB or MySQL
- mysql2
- JWT with
jsonwebtoken - bcrypt
- Multer
- dotenv
- CORS
Install these before starting:
- Node.js
- npm
- MariaDB or MySQL server
- MariaDB/MySQL command-line client
Check your tools:
node -v
npm -v
mysql --versionOn some machines the SQL client is named mariadb instead of mysql.
supplytrack-backend/
config/
db.js
controllers/
middleware/
routes/
uploads/
item-images/
database/
schema.sql
seed.sql
server.js
package.json
.env.example
README.md
Important folders:
config/db.js: creates the database connection pool.controllers/: contains the main business logic and SQL queries.middleware/: reusable request checks such as JWT auth, role checks, async error handling, and uploads.routes/: maps API endpoints to controller functions.uploads/item-images/: stores uploaded item images.database/schema.sql: creates all tables.database/seed.sql: inserts sample users, products, carts, orders, and tracking records.server.js: creates the Express app and mounts all routes.
Copy the example file:
cp .env.example .envDefault .env.example values:
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=root
DB_NAME=supplytrack_db
DB_PORT=3306
JWT_SECRET=supplytrack_student_secret
PORT=5000Do not hardcode database credentials in source code. Controllers and config files read settings from process.env.
If port 5000 is already being used on your machine, change PORT in .env, then update the frontend VITE_API_URL to match.
From this backend folder:
npm installLog in to MariaDB/MySQL:
mysql -uroot -prootCreate the database:
CREATE DATABASE IF NOT EXISTS supplytrack_db;
EXIT;Import the schema and seed data from this folder:
mysql -uroot -proot supplytrack_db < database/schema.sql
mysql -uroot -proot supplytrack_db < database/seed.sqlThe schema file drops and recreates tables. Running it again resets the database.
Verify seeded data:
mysql -uroot -proot supplytrack_db -e "SELECT id, full_name, email, role FROM users;"
mysql -uroot -proot supplytrack_db -e "SELECT COUNT(*) AS item_count FROM items;"Expected seed data includes:
- 1 admin
- 3 customers
- 3 suppliers
- 2 pickup station officers
- 3 pickup stations
- 6 item categories
- 12 sample items
- sample carts
- sample orders
- sample order tracking records
Development mode:
npm run devProduction-style start:
npm startOpen this URL to confirm the API is running:
http://localhost:5000
Expected response:
{ "message": "SupplyTrack API is running" }All seeded accounts use this password:
password123
Accounts:
- Admin:
admin@supplytrack.test - Customer:
customer@supplytrack.test - Supplier:
supplier@supplytrack.test - Pickup Station Officer:
station@supplytrack.test
A typical protected request works like this:
- The user logs in through
POST /api/auth/login. - The backend checks the email and bcrypt password hash.
- The backend returns a JWT.
- The frontend sends the token in the
Authorizationheader. authMiddleware.jsverifies the token and setsreq.user.roleMiddleware.jschecks whether the user has the correct role.- The controller runs the database query and returns JSON.
The login response includes a JWT. Protected routes expect this header:
Authorization: Bearer your_token_hereThe JWT contains basic user data such as user id, email, and role. It should not contain passwords or private secrets.
The system has four roles:
admincustomersupplierstation_officer
Examples:
- Customers can manage only their own cart and orders.
- Suppliers can manage only their own items and supplier orders.
- Station officers can update only orders assigned to their station.
- Admins can view and manage system-wide records.
Supplier item routes accept one file field named image.
Examples:
POST /api/itemsPUT /api/items/:id
Uploaded files are stored in:
uploads/item-images/
They are served publicly from:
/uploads/item-images/file-name.jpg
SupplyTrack uses this order movement:
Pending
Accepted
Rejected
Packed
In Transit to Pickup Station
Arrived at Pickup Station
Ready for Pickup
Picked Up
Completed
Cancelled
Normal successful flow:
Pending -> Accepted -> Packed -> In Transit to Pickup Station -> Arrived at Pickup Station -> Ready for Pickup -> Picked Up -> Completed
Special endings:
Rejected: supplier rejects the order.Cancelled: customer cancels before the order moves too far.
The orders table stores the current status.
The order_tracking table stores the full history of status changes. Each tracking row contains:
- order id
- status
- description
- user who made the update
- current location
- timestamp
This is useful because students can learn the difference between current state and historical records.
Auth:
POST /api/auth/registerPOST /api/auth/loginGET /api/auth/me
Items:
GET /api/itemsGET /api/items/:idPOST /api/itemsPUT /api/items/:idDELETE /api/items/:idGET /api/items/supplier/my-items
Cart:
GET /api/cartPOST /api/cart/itemsPUT /api/cart/items/:idDELETE /api/cart/items/:idDELETE /api/cart/clear
Orders:
POST /api/ordersGET /api/orders/my-ordersGET /api/orders/:idPUT /api/orders/:id/cancelPUT /api/orders/:id/confirm-received
Supplier Orders:
GET /api/supplier/ordersPUT /api/supplier/orders/:id/acceptPUT /api/supplier/orders/:id/rejectPUT /api/supplier/orders/:id/packedPUT /api/supplier/orders/:id/send-to-station
Pickup Station:
GET /api/pickup-stationsGET /api/station/ordersPUT /api/station/orders/:id/arrivedPUT /api/station/orders/:id/readyPUT /api/station/orders/:id/picked-up
Tracking:
GET /api/tracking/order/:orderId
Profiles:
GET /api/profilePUT /api/profile/supplierPUT /api/profile/customer
Categories:
GET /api/categoriesPOST /api/categoriesPUT /api/categories/:id
Admin:
GET /api/admin/statsGET /api/admin/usersGET /api/admin/itemsGET /api/admin/ordersGET /api/admin/pickup-stationsPOST /api/admin/pickup-stationsPUT /api/admin/pickup-stations/:idPUT /api/admin/users/:id/status
curl -X POST http://localhost:5000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"customer@supplytrack.test","password":"password123"}'If login works, the response includes:
messagetokenuser
Unknown database 'supplytrack_db'
Run the database setup commands again. This means the database has not been created or imported.
Access denied for user 'root'
Check DB_USER and DB_PASSWORD in .env.
ECONNREFUSED
MariaDB/MySQL is probably not running, or the backend port does not match the frontend URL.
Frontend login shows ERR_CONNECTION
Make sure the backend is running and the frontend .env uses the same backend port.
Uploaded images do not show
Check that VITE_UPLOADS_URL in the frontend points to the backend host and port.
- Start with
server.jsto see how Express is created. - Read
routes/authRoutes.js, thencontrollers/authController.js. - Read
middleware/authMiddleware.jsto understand JWT verification. - Read
middleware/roleMiddleware.jsto understand role checks. - Study
database/schema.sqlto understand table relationships. - Follow a customer order from cart to order in
orderController.js. - Follow status updates in
supplierOrderController.jsandstationController.js. - Query
order_trackingdirectly in SQL to see the history.