NESA TSS Practical Exam 2025/2026

SMS DAB Enterprise
Student Exam Guide

Everything the inspector will check, ask, and test — with exact answers from your code. Read this before your assessment.

15% — ERD & DFD
40% — Build & Code
35% — Demo & Quality
10% — Closing
📁
Every File & What It Does
The inspector may open any file. Know what's in each one and why it exists.
BACKEND — backend-project/src/
src/server.js
Entry Point — Express App
Creates the Express app, enables CORS for localhost:5173, parses JSON bodies, calls ensureDbAndTables() to auto-create the DB, then mounts all 4 route groups and starts listening on port 5000.
Express CORS Port 5000 nodemon
src/db.js
Database Setup — Auto-Create
Connects to MySQL, automatically creates the SMS database if it doesn't exist, then creates users, stock_in, and stock_out tables with all primary keys and foreign keys. Uses mysql2/promise connection pool.
MySQL2 Auto-DB Create FK Relations Pool
src/middleware/auth.js
JWT Auth Middleware
Reads the Authorization: Bearer <token> header from every protected request. Verifies the JWT using JWT_SECRET. Attaches req.user (id + username) for use in routes. Returns 401 if token is missing or invalid.
JWT Middleware Protected Routes
src/routes/auth.js
Auth Routes — Register & Login
POST /api/auth/register — hashes password with bcrypt (12 rounds), inserts user. POST /api/auth/login — fetches user, compares bcrypt hash, signs 8-hour JWT. Both validate required fields and return proper errors.
bcryptjs JWT Sign Register+Login
src/routes/stockIn.js
StockIn CRUD Routes
Full CRUD: GET all (with ?search= filter on ItemName, SupplierName, Description), GET by id, POST (auto-calculates cumulative TotalQuantityIn using a transaction), PUT, DELETE (blocked if stock_out records exist). All routes require JWT.
GET/POST/PUT/DELETE Search Transaction Auth Required
src/routes/stockOut.js
StockOut CRUD Routes
Full CRUD with stock validation — checks remaining quantity before issuing. Calculates cumulative TotalQuantityOut per stockInId. GET joins stock_in and users tables for full record view. All routes require JWT.
CRUD Stock Validation JOIN Query Auth Required
src/routes/reports.js
Daily Stock Status Report
GET /api/reports/stock-status — runs a SQL query that GROUP BY ItemName, SUM(QuantityIn), SUM(QuantityOut), and calculates RemainingQuantity. Returns all 8 product items with their complete stock status. Requires JWT.
Report GROUP BY LEFT JOIN Auth Required
.env
Environment Variables
Stores DB_HOST, DB_USER, DB_PASSWORD, DB_NAME (SMS), DB_PORT, JWT_SECRET, and PORT. Loaded by dotenv at startup. Keeps secrets out of source code.
dotenv Secrets Config
FRONTEND — frontend-project/src/
src/main.jsx
React Entry Point
Mounts the React app into index.html's #root div using ReactDOM. Imports global CSS. This is where React starts.
ReactDOMEntry
src/App.jsx
Router & Protected Routes
Sets up React Router v6 with 4 routes: /login, /, /stockin, /stockout, /reports. Wraps protected routes with a Protected component that redirects to /login if no user. Wraps everything in AuthProvider.
React Router v6 Protected Routes AuthProvider
src/context/AuthContext.jsx
Auth State — React Context
Creates a React Context with login/logout functions. login(token, username) saves to localStorage. logout() clears it. On page refresh, reads from localStorage to restore session. The useAuth() hook gives any component access.
Context API useState localStorage
src/api/index.js
Axios Client — API Layer
Creates axios with baseURL http://localhost:5000/api. Request interceptor automatically attaches Authorization: Bearer <token> from localStorage to every request. Exports named functions for all CRUD operations and the report.
Axios Interceptor Auto Auth Header
src/pages/LoginPage.jsx
Login & Register UI
Single page with toggle between Login and Register. Form validation (password min 8 chars on register). Calls /api/auth/login or /api/auth/register. On success saves token, redirects to /. Fully styled with Tailwind + emerald theme.
useState Validation Form Tailwind
src/pages/StockInPage.jsx
StockIn CRUD Interface
Full CRUD page: lists all stock-in records, search bar, Add/Edit form, Delete with confirmation. Uses useState for form state, useEffect to fetch data. Calls all StockIn API functions. Displays User_Name who recorded each entry.
CRUD useState+useEffect Search Tailwind
src/pages/StockOutPage.jsx
StockOut CRUD Interface
Full CRUD page for stock-out records. Fetches stock-in records to populate a dropdown for selecting which item is being issued. Validates quantity against available stock. Same pattern as StockInPage with appropriate fields.
CRUD Dropdown from API Stock Validation
src/pages/ReportsPage.jsx
Daily Stock Status Report
Fetches from /api/reports/stock-status and renders a table showing: ItemName, TotalQuantityReceived, TotalQuantityIssuedOut, RemainingQuantity for all items. Styled table with consistent colors.
Report Table useEffect 4 Columns
src/components/Navbar.jsx
Navigation Bar
Top navigation with links to Stock In, Stock Out, and Reports. Shows the logged-in username. Logout button calls useAuth().logout() and redirects to /login. Uses React Router's <Link> component.
Link Logout Navigation
tailwind.config.js
Tailwind CSS Config
Configures Tailwind to scan all .jsx files in src/. This is how Tailwind knows which classes to include in the final CSS bundle.
TailwindConfig
vite.config.js
Vite Build Config
Configures Vite with the React plugin. Vite is the build tool + dev server that compiles JSX and serves the frontend on localhost:5173.
ViteBuild Tool
🔌
All API Endpoints
Every endpoint, method, and whether it needs a token. Base URL: http://localhost:5000
💡
The inspector may ask you to test any endpoint live. Backend must be running first (START BACKEND.bat or cd backend-project && npm start). Use the browser or a tool like Postman/Thunder Client.
Method Endpoint What It Does Auth? Body / Params
POST /api/auth/register Create new user account, bcrypt hashes password No { username, password }
POST /api/auth/login Login, returns JWT token valid 8 hours No { username, password }
GET /api/stockin Get all stock-in records (optional search) Bearer ?search=cement
GET /api/stockin/:id Get one stock-in record by ID Bearer
POST /api/stockin Add new stock-in, auto-calculates TotalQuantityIn Bearer { ItemName, Description, QuantityIn, SupplierName, StockInDate }
PUT /api/stockin/:id Update existing stock-in record Bearer Same as POST body
DELETE /api/stockin/:id Delete stock-in (blocked if stock-out records exist) Bearer
GET /api/stockout Get all stock-out records (optional search) Bearer ?search=kevin
GET /api/stockout/:id Get one stock-out record by ID Bearer
POST /api/stockout Issue stock out, validates remaining stock first Bearer { QuantityOut, StockOutDate, stockInId }
PUT /api/stockout/:id Update stock-out record Bearer Same as POST body
DELETE /api/stockout/:id Delete stock-out record Bearer
GET /api/reports/stock-status Daily report: all items with total in, out, remaining Bearer
GET /health Check if server is running — returns { ok: true } No
🔐
Authentication System — Full Explanation
The inspector WILL ask about this. Know exactly how it works.
METHOD USED
JWT — JSON Web Token
Not sessions. When you login, the server signs a JWT token with your user id and username. Token expires in 8 hours. The client stores it in localStorage with key sms_token. Every request to protected endpoints sends it as Authorization: Bearer <token>.
JWT 8h expiry Bearer Token
PASSWORD SECURITY
bcryptjs — Hash + Salt
Passwords are never stored as plain text. When registering, bcrypt.hash(password, 12) creates a salted hash (12 rounds). When logging in, bcrypt.compare(password, hash) verifies it. Even if the database is leaked, passwords cannot be read.
bcryptjs Salt rounds: 12 One-way hash
DUPLICATE PREVENTION
DB-level UNIQUE constraint
The users table has User_Name VARCHAR(100) NOT NULL UNIQUE. If you try to register with an existing username, MySQL throws ER_DUP_ENTRY and the backend returns { error: "Username already exists" }.
UNIQUE ER_DUP_ENTRY Validation
FRONTEND PERSISTENCE
AuthContext + localStorage
Token + username saved as sms_token and sms_user in localStorage. On page refresh, AuthContext reads them back — so user stays logged in. Axios interceptor in api/index.js reads sms_token and adds the header automatically to every request.
Context API localStorage Interceptor
⚠️
If the inspector asks "how would you test authentication?": open the browser DevTools → Application → localStorage. You'll see sms_token after login. Then try accessing /api/stockin without the token — you get 401 Unauthorized.
💬
Inspector Questions & Your Answers
Click each question to see the exact answer based on your code. These are real questions inspectors ask at NESA TSS exams.
1
Describe your product in brief — what does it do?
This is a Store Management System (SMS) built for DAB Enterprise LTD — a company in Kigali City that sells building tools and construction materials. The system is a web application that replaces their manual paper-based tracking. It allows the company to:
  • Record items received into the store (Stock In) — products like Cement, Steel bars, Ceramic tiles, etc.
  • Record items issued out of the store (Stock Out)
  • Track which employee (User) performed each transaction
  • Generate a daily stock status report showing remaining quantities for every item
  • Manage user accounts with secure login
It's built with Node.js + Express on the backend and React.js + Tailwind CSS on the frontend, with a MySQL database.
2
Which authentication method are you using and why?
The system uses JWT (JSON Web Token) authentication, not session-based authentication.

How it works step by step:
  • User enters username and password on the login form
  • Frontend sends a POST request to /api/auth/login
  • Backend fetches the user from MySQL, compares the password to the bcrypt hash using bcrypt.compare()
  • If correct, the server signs a JWT token with jwt.sign({ id, username }, JWT_SECRET, { expiresIn: '8h' })
  • Token is sent back and stored in localStorage as sms_token
  • Every future request includes the header Authorization: Bearer <token>
  • The auth middleware in middleware/auth.js verifies it with jwt.verify()
Why JWT? It is stateless — the server doesn't need to store sessions. It is scalable and the standard for REST APIs.
3
How do you test your endpoints? Show me.
There are 3 ways to test the endpoints:

1. Through the Frontend UI (easiest to show inspector):
  • Open http://localhost:5173 in the browser
  • Register an account, then login
  • Use the forms on Stock In and Stock Out pages
  • Check the Reports page to see the result
2. Using Browser DevTools:
  • Open DevTools (F12) → Network tab
  • Every API call made by the frontend shows here with request/response details
3. Direct API test:
  • Open http://localhost:5000/health in browser → should show {"ok":true}
  • Open http://localhost:5000/api/stockin without token → should show {"error":"Unauthorized"}
Health Check — paste in browser address bar
http://localhost:5000/health
4
How does your database auto-create? Show me the code.
In src/db.js, the ensureDbAndTables() function runs at server startup. It:
  • First connects to MySQL without specifying a database
  • Runs CREATE DATABASE IF NOT EXISTS SMS
  • Then creates a connection pool to the SMS database
  • Runs CREATE TABLE IF NOT EXISTS users (...)
  • Runs CREATE TABLE IF NOT EXISTS stock_in (...)
  • Runs CREATE TABLE IF NOT EXISTS stock_out (...) with foreign keys to both stock_in and users
This means the examiner only needs to have MySQL running — they don't need to create any database or tables manually. The app creates everything automatically.
Key line from db.js
await conn.query(`CREATE DATABASE IF NOT EXISTS \`SMS\``);
5
What are the entities and relationships in your database?
The database has 3 tables (entities):
  • users — id (PK), User_Name, Password, created_at
  • stock_in — id (PK), ItemName, Description, QuantityIn, TotalQuantityIn, SupplierName, StockInDate, userId (FK → users.id), created_at
  • stock_out — id (PK), QuantityOut, TotalQuantityOut, StockOutDate, stockInId (FK → stock_in.id), userId (FK → users.id), created_at
Relationships:
  • One User can record many StockIn records (1:N)
  • One User can record many StockOut records (1:N)
  • One StockIn record can have many StockOut records (1:N) — e.g. Cement received in bulk can be issued out multiple times
Both foreign keys in stock_out use ON DELETE CASCADE, meaning if a parent record is deleted, the child records are deleted too.
6
How does the search functionality work?
Search works on both Stock In and Stock Out pages.

On the frontend: There's a search input field. As the user types and submits, the search term is passed as a query parameter to the API, e.g. GET /api/stockin?search=cement

On the backend (stockIn.js): If a search param exists, the SQL query adds a WHERE clause using LIKE:
SQL Filter in stockIn.js
AND (s.ItemName LIKE ? OR s.SupplierName LIKE ? OR s.Description LIKE ?)
The %cement% pattern matches any record containing the word "cement" anywhere in the field. For Stock Out, search works on ItemName and User_Name.
7
How is the daily stock status report generated?
The report is generated by the GET /api/reports/stock-status endpoint in src/routes/reports.js.

It runs a single SQL query that:
  • JOINs stock_in with stock_out using LEFT JOIN (to include items with zero stock-out)
  • Groups by ItemName
  • Calculates SUM(QuantityIn) as TotalQuantityReceived
  • Calculates COALESCE(SUM(QuantityOut), 0) as TotalQuantityIssuedOut (0 if never issued)
  • Subtracts to get RemainingQuantity
The SQL query from reports.js
SELECT
  si.ItemName,
  SUM(si.QuantityIn) AS TotalQuantityReceived,
  COALESCE(SUM(so.QuantityOut), 0) AS TotalQuantityIssuedOut,
  SUM(si.QuantityIn) - COALESCE(SUM(so.QuantityOut), 0) AS RemainingQuantity
FROM stock_in si
LEFT JOIN stock_out so ON so.stockInId = si.id
GROUP BY si.ItemName
ORDER BY si.ItemName ASC
The frontend ReportsPage fetches this and displays it as a styled table.
8
What difficulties did you face and how did you overcome them?
Difficulty 1: Calculating TotalQuantityIn automatically
The challenge was that TotalQuantityIn should be a running cumulative total for the same item across all transactions, not just the current record. I solved this by using a MySQL transaction — before inserting, I query SELECT COALESCE(SUM(QuantityIn),0) FROM stock_in WHERE ItemName=? to get the existing total, then add the new quantity.

Difficulty 2: Preventing over-issuing stock
When recording Stock Out, the system needs to verify there's enough stock remaining. I solved this by querying TotalQuantityIn and comparing against existing TotalQuantityOut before allowing the insert. If QuantityOut > remaining, the API returns a 400 error with the exact remaining quantity.

Difficulty 3: CORS between frontend and backend
The frontend runs on port 5173 and backend on 5000. Browsers block cross-origin requests by default. I fixed this by configuring CORS in server.js: cors({ origin: 'http://localhost:5173', credentials: true }).
9
What React hooks are you using and what do they do?
The project uses 3 React hooks:

useState: Manages local component state. Used for:
  • Form field values (e.g. the Add StockIn form)
  • The list of records fetched from API
  • Loading states and error messages
useEffect: Runs side effects. Used to:
  • Fetch all stock records when the page first loads (useEffect(() => { fetchData() }, []))
  • Re-fetch data after create/update/delete operations
useContext (via useAuth custom hook): Reads authentication state from AuthContext. Used in:
  • Navbar — to show the logged-in username and logout button
  • LoginPage — to call the login() function after successful login
  • App.jsx Protected component — to check if user exists before allowing access
10
How does routing work in your React app?
The app uses React Router v6 (installed as react-router-dom).

In App.jsx:
  • BrowserRouter wraps the entire app to enable URL-based routing
  • Routes and Route components map URLs to page components
  • /login → LoginPage (public)
  • /stockin, /stockout, /reports → wrapped in <Protected>
  • Protected component checks useAuth().user — if null, redirects to /login with <Navigate>
The Navbar uses <Link to="/stockin"> for client-side navigation (no full page reload).
11
What is the purpose of the .env file?
The .env file stores environment-specific configuration as key-value pairs. It is loaded by the dotenv package when the server starts (require('dotenv').config()).

Variables stored:
  • DB_HOST — MySQL server hostname (localhost)
  • DB_USER — MySQL username (root)
  • DB_PASSWORD — MySQL password
  • DB_NAME — Database name (SMS)
  • DB_PORT — MySQL port (3306)
  • JWT_SECRET — Secret key used to sign JWT tokens
  • PORT — Port the Express server listens on (5000)
Why? So passwords and secrets are not hardcoded in source files. Different environments (development, production) can have different values without changing code.
12
What happens if you try to add stock-out that exceeds available stock?
The backend prevents this at the API level. In src/routes/stockOut.js, the POST endpoint:
  • Fetches the StockIn record to get TotalQuantityIn
  • Queries total already issued: SELECT SUM(QuantityOut) FROM stock_out WHERE stockInId=?
  • Calculates remaining = TotalQuantityIn - existingTotal
  • If QuantityOut > remaining → returns HTTP 400 with error message: "Insufficient stock. Remaining: X"
The frontend also shows this error message to the user. The database transaction is rolled back so no partial data is saved.
13
What is CORS and why do you need it?
CORS stands for Cross-Origin Resource Sharing. Browsers enforce a security policy that blocks JavaScript from making requests to a different origin (different port = different origin).

The problem: The React frontend runs on localhost:5173 (Vite dev server). The Express backend runs on localhost:5000. When the frontend tries to call the backend, the browser blocks it by default.

The solution: In server.js, the CORS middleware is configured to allow requests from http://localhost:5173 specifically:
CORS config in server.js
app.use(cors({ origin: 'http://localhost:5173', credentials: true }));
This tells the browser: "it is safe to receive responses from this server even though the ports are different."
14
How is Tailwind CSS set up and used in this project?
Installation: Tailwind was installed as a dev dependency along with PostCSS and Autoprefixer.

Configuration files:
  • tailwind.config.js — tells Tailwind to scan ./src/**/*.{js,jsx} files for class names
  • postcss.config.js — PostCSS plugin that processes Tailwind
  • src/index.css — contains the 3 Tailwind directives: @tailwind base, @tailwind components, @tailwind utilities
Usage: Utility classes are applied directly in JSX, e.g.:
Example from LoginPage.jsx
<div className="min-h-screen bg-gradient-to-br from-emerald-900 to-emerald-700">
Responsive classes like max-w-7xl, flex, grid, px-4, py-8 handle layout and spacing. Hover states like hover:bg-emerald-900 handle interactions.
15
What is nodemon and why is it installed?
Nodemon is a development tool that automatically restarts the Node.js server whenever a file changes. Without it, every time you edit backend code you would have to manually stop and restart the server.

It is installed as a devDependency in backend-project/package.json. The dev script in package.json uses it:
package.json scripts
"dev": "nodemon src/server.js",
"start": "node src/server.js"
For the exam, npm start (using plain node) is fine since we are not actively editing code.
🖥
Step-by-Step Demo for Inspector
Do this in order when the inspector says "show me your product." Don't skip steps.
1
Start the Backend
Double-click START BACKEND.bat OR open terminal in backend-project/ and run npm start. Wait until you see "✅ SMS Backend → http://localhost:5000" in the console. This also auto-creates the SMS database and tables.
2
Start the Frontend
Double-click START FRONTEND.bat OR open terminal in frontend-project/ and run npm run dev. Open http://localhost:5173 in Chrome.
3
Register an Account
Click "Create Account", enter a username and password (min 8 chars). Show that duplicate usernames are rejected. After success, switch back to Login tab.
4
Login
Enter credentials and login. Point out that you are redirected to the Stock In page automatically. The Navbar shows your username. Show DevTools → Application → localStorage → sms_token exists.
5
Add Stock In Records
Add at least 3 stock items using the real product names: Cement, Steel bars, Ceramic tiles, Wheelbarrows, Painting brush, Color Paint, Masonry nails, Iron sheets. These are the 8 exact items from the exam specification. Add a supplier name and quantity for each.
6
Edit and Delete Stock In
Show the Edit button (pre-fills the form with existing data). Save the update. Then try to delete — show it works. Show the search bar working by searching "Cement".
7
Add Stock Out Records
Go to Stock Out page. Select a StockIn item from the dropdown, enter quantity and date. Show the stock validation — try to issue more than available and show the error. Then issue a valid amount.
8
Show the Report
Navigate to Reports. Show the table with: Item Name, Total Received, Total Issued Out, Remaining Quantity. This is the "daily stock status report" the exam requires. Point out the 4 columns explicitly.
9
Show Responsiveness
In Chrome DevTools, toggle device toolbar (Ctrl+Shift+M). Select a mobile size like iPhone 12. Show that the layout adjusts — this proves responsive design. Tailwind's mobile-first classes handle this.
📊
Score Breakdown — Where Your Marks Come From
Total: 100 marks. The project covers almost everything. ERD & DFD you do on paper before touching the computer.
📐
Preliminary Activities
(ERD + DFD)
15%
31 marks max
Draw StockIn entity + attributes (2+2)
Draw StockOut entity + attributes (2+2)
Draw Users entity + attributes (1+1)
Connect entities + show relationships (2+1+1)
Indicate all PKs and FKs (1+1+1+1+1+1+1)
Context Diagram with all external entities (10)
⚙️
Build & Code Quality
(Process & Fulfillment)
40%
137 marks max
MySQL + Apache running (2)
React project + Router + Axios installed (7)
Node.js + Express + CORS + Nodemon (6)
3 DB tables with PKs and FKs (12)
React components, hooks, forms (25)
React Router + navigation (7)
Tailwind CSS + responsive (16)
Express server + DB connection (17)
REST API endpoints: GET/POST/PUT/DELETE (8)
Axios integration + API files (14)
Auth: validation, bcrypt, no duplicates (7)
🎤
Demo + Quality
(Presentation & Usability)
35%
35 marks max
State product name + key steps (4)
Explain function + importance (2)
State difficulties + solutions (2)
Audible + use body language (2)
All CRUD operations work (5)
Search works (1)
Styled lists, interactive buttons (6)
Report: 4 columns displayed (8)
Consistent colors and fonts (3)
🧹
Closing Activities
(Hygiene & Best Practices)
10%
12 marks max
Paper clean, no excessive erasures (3)
Waste disposed properly (1)
Stop Apache + MySQL services (2)
Do NOT uninstall text editor (1)
Do NOT uninstall XAMPP (1)
Respect time limit (2)
Pre-Exam Checklist
Verify every item before the inspector arrives. This is your final check.
⚠️
Remember: Draw the ERD and DFD on A4 paper with a pencil BEFORE touching the computer. The inspector collects the paper drawings. Make sure your full name and index number are on each page.
🗂 Paper Work (Before Computer)
ERD drawn on A4 paper — all 3 entities (Users, StockIn, StockOut)
All attributes included for each entity
PKs labeled (PK) and FKs labeled (FK)
Relationship lines between entities with cardinality (1:N)
Level 0 DFD (Context Diagram) drawn — one central process, external entities, data flows with labels
Your name + index number on BOTH papers
💻 Computer Setup
XAMPP running — Apache green, MySQL green
Project folder named correctly: FirstName_LastName_National_Practical_Exam_2026
Backend starts without errors (npm start in backend-project/)
Frontend starts and opens at localhost:5173
SMS database auto-created in MySQL (check phpMyAdmin or MySQL Workbench)
All 3 tables exist: users, stock_in, stock_out
Can register a new user account successfully
Can login and see the dashboard
🧪 All Features Work
Add Stock In with all 8 product items (Steel bars, Wheelbarrows, Ceramic tiles, Cement, Painting brush, Color Paint, Masonry nails, Iron sheets)
Edit a Stock In record
Delete a Stock In record
Search on Stock In page works
Add Stock Out record
Stock validation works (error when issuing too much)
Edit and Delete Stock Out work
Reports page shows 4 columns correctly
RemainingQuantity is mathematically correct
Logout button works and redirects to login
Accessing protected routes without login redirects to /login
App is responsive on mobile size (DevTools toggle)
🎤 Verbal Presentation Ready
Can describe the product in 2 sentences clearly
Can explain what StockIn and StockOut mean in context of DAB Enterprise
Can explain JWT authentication from memory
Can name at least 2 real difficulties and solutions
Can show the database tables in phpMyAdmin or terminal
Speak clearly and use good body language (look at inspector)
🏁
After assessment: Stop Apache and MySQL from XAMPP. Do NOT uninstall anything. The inspector checks that services are stopped but tools remain installed. Hand over your ERD/DFD papers. Your work folder gets removed by the inspector as per the rules.
SMS DAB Enterprise LTD — NESA TSS Practical Exam Guide 2025/2026  •  Kigali, Rwanda  •  Node.js + React.js + MySQL