A full-stack Point-of-Sale system built for a real service station in Quebec, Canada.
Handles dual sales models — prepaid fuel dispensing (DIESEL/DEF) and in-store retail — with role-based access control, real-time analytics, inventory tracking, receipt generation, and Quebec tax compliance.
Features • Tech Stack • Architecture • Getting Started • API Reference • Screenshots
Most POS tutorials stop at a shopping cart. This system solves real operational problems at a gas station:
- Prepaid fuel authorization — A customer authorizes $500, fuels $450 worth of diesel, and is only charged $450. The system tracks both the authorized ceiling and the actual charge, preventing overcharges while maintaining an audit trail.
- Quebec tax compliance — Automatically applies 5% GST + 9.975% QST to every transaction with itemized breakdowns on receipts.
- Dual sales model — A single cashier interface handles both fuel pump transactions and retail counter sales, with distinct workflows unified under one dashboard.
- Shift accountability — Role-based access ensures cashiers can only operate the POS, while managers handle inventory and admins control user access.
- Unified interface for fuel pump transactions and retail counter sales
- Prepaid fuel workflow: authorize amount, dispense fuel, charge actual amount
- Real-time cart with quantity adjustment, item removal, and live tax calculation
- Payment method selection (Cash, Credit Card, Debit Card, Mobile Pay)
- Pump status monitoring (IDLE / ACTIVE / ERROR)
- Auto-generated receipt numbers (
IRV24-YYYYMMDD-00001daily sequence) - Professional receipt layout with business info and tax breakdown
- QR code embedded per transaction for digital verification
- Browser print integration via
react-to-print
- Daily stats — Revenue, transaction count, average transaction value
- Fuel vs. Retail split — Separate revenue and volume metrics
- Payment breakdown — Pie chart by payment method
- Trend analysis — Area charts with 7/14/30-day and custom date ranges
- Range reports — Daily revenue breakdown over any date period
- Real-time stock levels with search, category filter, and sorting
- Low-stock alerts with configurable thresholds
- Out-of-stock flagging with visual indicators
- Restock and adjustment logging (damage, correction) with operator tracking
- Complete audit trail per product via
StockAdjustmentmodel
- Paginated transaction log with date range, payment method, and sale type filters
- Click-to-expand transaction details with itemized breakdown
- Reprint receipts from any past transaction
| Role | POS | Transactions | Dashboard | Inventory | Admin |
|---|---|---|---|---|---|
| Admin | Full | Full | Full | Full (read/write) | Full |
| Manager | Full | Full | Full | Full (read/write) | — |
| Cashier | Full | View own | View | View only | — |
- JWT authentication with 30-day token expiry
- Bcrypt password hashing (salt rounds: 10)
- Helmet HTTP security headers
- Rate limiting — 200 req/15min (API), 20 req/15min (auth)
- Input validation and sanitization via
express-validator - CORS restricted to frontend origin
- Auto-logout on 401 responses
- Winston structured JSON logging (file + console transports)
- Morgan HTTP request logging piped to Winston
- Rotating log files (5MB max, 5 file retention)
- Separate error log and combined log
| Package | Version | Purpose |
|---|---|---|
| Express.js | 5.1 | REST API framework |
| MongoDB + Mongoose | 8.17 | Document database with schema validation |
| jsonwebtoken | 9.0 | JWT authentication |
| bcryptjs | 3.0 | Password hashing |
| express-validator | 7.3 | Request validation and sanitization |
| express-rate-limit | 8.2 | Brute-force protection |
| Helmet | 8.1 | Security headers |
| Winston | 3.19 | Application logging |
| Morgan | 1.10 | HTTP request logging |
| Jest + Supertest | 30.2 / 7.2 | Testing (in-memory MongoDB) |
| Package | Version | Purpose |
|---|---|---|
| React | 19.1 | UI framework |
| Vite | 7.0 | Build tool with HMR |
| Tailwind CSS | 4.1 | Utility-first styling |
| React Router | 7.13 | Client-side routing |
| Axios | 1.13 | HTTP client with interceptors |
| Recharts | 3.7 | Analytics charts |
| react-to-print | 3.2 | Receipt printing |
| qrcode.react | 4.2 | QR code generation |
| react-datepicker | 9.1 | Date range selection |
| react-hot-toast | 2.6 | Toast notifications |
┌─────────────────────────────────────────────────────────┐
│ FRONTEND │
│ │
│ React 19 + Vite + Tailwind CSS │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ │
│ │ POS │ │Dashboard │ │ Inventory │ │ Admin │ │
│ │ Page │ │ Charts │ │ Manager │ │ Panel │ │
│ └────┬─────┘ └────┬─────┘ └─────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴─────────────┴─────────────┴────┐ │
│ │ Service Layer (Axios) │ │
│ │ authService │ saleService │ productService │ ... │ │
│ └──────────────────────┬────────────────────────────┘ │
│ │ JWT Bearer Token │
└─────────────────────────┼───────────────────────────────┘
│ HTTP / REST
┌─────────────────────────┼───────────────────────────────┐
│ BACKEND │
│ │ │
│ ┌──────────────────────┴────────────────────────────┐ │
│ │ Express.js 5 + Middleware │ │
│ │ Helmet │ CORS │ Rate Limit │ Morgan │ Validator │ │
│ └──────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴────────────────────────────┐ │
│ │ Route Handlers │ │
│ │ /auth │ /sales │ /products │ /pumps │ /inventory │ │
│ └──────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴────────────────────────────┐ │
│ │ Middleware: protect + requireRole │ │
│ └──────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴────────────────────────────┐ │
│ │ Mongoose Models + Pre-save Hooks │ │
│ │ User │ Product │ Pump │ Sale │ StockAdjustment │ │
│ └──────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴────────────────────────────┐ │
│ │ Winston Logger │ │
│ │ error.log │ combined.log │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────┼───────────────────────────────┘
│
┌────────┴────────┐
│ MongoDB │
│ Irving24 DB │
└─────────────────┘
FuelSystem/
├── backend/
│ ├── app.js # Express app (middleware, routes)
│ ├── server.js # Entry point (DB connect + listen)
│ ├── config/
│ │ ├── db.js # MongoDB connection
│ │ └── logger.js # Winston configuration
│ ├── middleware/
│ │ └── authMiddleware.js # JWT verify + role guard
│ ├── models/
│ │ ├── User.js # Auth + roles
│ │ ├── Product.js # Catalog (fuel + retail)
│ │ ├── Pump.js # Pump state machine
│ │ ├── Sale.js # Transactions + receipt gen
│ │ └── StockAdjustment.js # Inventory audit trail
│ ├── routes/
│ │ ├── authRoutes.js # Login / Register
│ │ ├── saleRoutes.js # In-store + fuel sales, stats
│ │ ├── productRoutes.js # CRUD products
│ │ ├── pumpRoutes.js # Pump control
│ │ ├── userRoutes.js # User management (admin)
│ │ └── inventoryRoutes.js # Stock ops + history
│ └── tests/
│ ├── setup.js # MongoMemoryServer lifecycle
│ ├── auth.test.js # 8 auth tests
│ └── sales.test.js # 7 sales tests
│
├── frontend/my-fuel-system/
│ └── src/
│ ├── App.jsx # Router configuration
│ ├── main.jsx # Entry point + BrowserRouter
│ ├── context/
│ │ └── AuthContext.jsx # Global auth state
│ ├── pages/
│ │ └── POS.jsx # Main POS interface
│ ├── components/
│ │ ├── Layout.jsx # Shared header + navigation
│ │ ├── ProtectedRoute.jsx # Auth + role guard
│ │ ├── Login.jsx # Authentication screen
│ │ ├── Receipt.jsx # Printable receipt + QR
│ │ ├── Dashboard.jsx # Analytics + charts
│ │ ├── TransactionHistory.jsx # Sale log + filters
│ │ ├── InventoryManagement.jsx# Stock management
│ │ ├── AdminPanel.jsx # User CRUD
│ │ └── ErrorBoundary.jsx # Error fallback
│ └── services/
│ ├── api.js # Axios instance + interceptors
│ ├── authService.js
│ ├── saleService.js
│ ├── productService.js
│ ├── pumpService.js
│ ├── userService.js
│ └── inventoryService.js
│
└── README.md
git clone https://github.com/mostafammagdy/FuelSystem.git
cd FuelSystemcd backend
npm installCreate a .env file in backend/:
MONGODB_URI=mongodb://localhost:27017/Irving24
JWT_SECRET=your_strong_secret_key_here
PORT=5000
CORS_ORIGIN=http://localhost:5173Start the server:
npm startcd frontend/my-fuel-system
npm install
npm run devThe app will be running at http://localhost:5173.
Open MongoDB shell and run:
use Irving24
// Create admin (password: admin123)
db.users.insertOne({
username: "admin",
password: "$2a$10$XQJG5cQYEOmrsGQBEd/jnOZABDxGRm0rKp6S1x2L3yZPvAYjH0Km",
role: "admin",
createdAt: new Date()
})
// Add fuel products
db.products.insertMany([
{ name: "DIESEL", category: "Fuel", price: 1.65, stock: 10000, unit: "liter", isFuel: true, fuelPumpId: "PUMP-01", createdAt: new Date() },
{ name: "DEF", category: "Fuel", price: 1.25, stock: 5000, unit: "liter", isFuel: true, fuelPumpId: "PUMP-02", createdAt: new Date() }
])
// Add pumps
db.pumps.insertMany([
{ pumpId: "PUMP-01", fuelType: "DIESEL", status: "IDLE", createdAt: new Date() },
{ pumpId: "PUMP-02", fuelType: "DEF", status: "IDLE", createdAt: new Date() }
])cd backend
npm testAll 15 tests run against an in-memory MongoDB instance — no external database needed.
| Method | Endpoint | Body | Response |
|---|---|---|---|
POST |
/api/auth/register |
{ username, password } |
{ _id, username, role, token } |
POST |
/api/auth/login |
{ username, password } |
{ _id, username, role, token } |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/sales/instore |
Record retail sale (auto-decrements stock) |
POST |
/api/sales/fuel |
Record fuel sale with pump + liters |
GET |
/api/sales |
List sales (paginated, filterable) |
GET |
/api/sales/stats/daily?date=YYYY-MM-DD |
Revenue, counts, payment breakdown |
GET |
/api/sales/stats/range?start=...&end=... |
Multi-day trend data |
GET |
/api/sales/:id |
Single transaction details |
In-store sale request body
{
"items": [
{ "productId": "64f...", "quantity": 2 },
{ "productId": "64f...", "quantity": 1 }
],
"totalAmount": 14.97,
"paymentMethod": "Credit Card"
}Fuel sale request body
{
"pumpId": "PUMP-01",
"fuelType": "DIESEL",
"fuelProductId": "64f...",
"quantity": 45.5,
"totalAmount": 75.08,
"authorizedAmount": 500.00,
"paymentMethod": "Credit Card"
}| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/products |
Public | List all products |
POST |
/api/products |
Admin/Manager | Create product |
PUT |
/api/products/:id |
Admin/Manager | Update product |
DELETE |
/api/products/:id |
Admin/Manager | Delete product |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/pumps |
List all pumps with status |
POST |
/api/pumps |
Register new pump |
POST |
/api/pumps/:pumpId/unlock |
Set pump to ACTIVE |
POST |
/api/pumps/:pumpId/lock |
Set pump to IDLE |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/inventory |
Products with stock (search, filter, sort) |
GET |
/api/inventory/low-stock?threshold=10 |
Below-threshold items |
GET |
/api/inventory/history/:productId |
Adjustment audit trail |
PUT |
/api/inventory/:productId/restock |
Add stock (admin/manager) |
PUT |
/api/inventory/:productId/adjust |
Log damage/correction |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/users |
List all users |
POST |
/api/users |
Create user with role |
PUT |
/api/users/:id |
Update user / change role |
DELETE |
/api/users/:id |
Remove user |
Add your screenshots here. Take screenshots of each screen and place them in a
docs/screenshots/folder, then uncomment the lines below.
The backend includes 15 integration tests running against an in-memory MongoDB instance via mongodb-memory-server:
PASS tests/auth.test.js
Auth Endpoints
POST /api/auth/register
✓ registers a new user and returns token
✓ rejects duplicate username
✓ rejects short username
✓ rejects short password
POST /api/auth/login
✓ logs in with valid credentials
✓ rejects wrong password
✓ rejects non-existent user
✓ rejects empty fields
PASS tests/sales.test.js
Sales Endpoints
POST /api/sales/instore
✓ creates an in-store sale and decrements stock
✓ rejects unauthenticated requests
✓ rejects sale with insufficient stock
✓ rejects invalid payment method
GET /api/sales
✓ returns paginated sales
GET /api/sales/stats/daily
✓ returns daily statistics
Health Check
✓ returns ok status
Test Suites: 2 passed, 2 total
Tests: 15 passed, 15 total
| Variable | Required | Default | Description |
|---|---|---|---|
MONGODB_URI |
Yes | — | MongoDB connection string |
JWT_SECRET |
Yes | — | Secret key for JWT signing |
PORT |
No | 5000 |
Server port |
CORS_ORIGIN |
No | http://localhost:5173 |
Allowed frontend origin |
NODE_ENV |
No | development |
Environment mode |
LOG_LEVEL |
No | info |
Winston log level |
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_API_BASE_URL |
No | http://localhost:5000 |
Backend API URL |
This project is for educational and portfolio purposes.
Built with Express.js, MongoDB, React, and a lot of coffee.
Designed for Irving24 Service Station, Quebec, Canada