Supplier relations for teams that buy and sell.
A MERN web application for managing relationships between Suppliers and Buyers. Suppliers publish articles (offers); Buyers browse them, leave reviews and ratings, and record transactions. Aggregated transaction data feeds a supplier performance report.
The objective is to facilitate and optimize supplier relationship management by improving data quality, transaction traceability, cost and lead-time reduction, and the satisfaction of internal and external customers.
- Registration and login for two distinct actor types, with email confirmation
- JWT authentication with a "remember me" long-lived token, and OTP-based password reset
- Supplier directory with CRUD, plus per-supplier article listings
- Article catalogue with embedded reviews and a denormalized rating average
- Standalone supplier ratings, separate from article reviews
- Transaction records (amount spent, deadline met, quality of service)
- A generated supplier performance report derived from transactions
Two independent applications. There is no root package.json — each app is installed
and run from its own directory.
flowchart LR
U[Browser] --> FE["React SPA (CRA)<br/>localhost:3000"]
FE -->|"REST — fetch / axios"| BE["Express API<br/>localhost:8000"]
BE -->|Mongoose| DB[(MongoDB)]
BE -->|Nodemailer / SMTP| MAIL[Gmail SMTP]
Backend request flow: server.js wires middleware (logger → cors → helmet → morgan →
body-parser → cookie-parser), mounts the routers, and ends with middleware/errorHandler.js.
Layering is routes → controllers → Mongoose models, with controllers wrapped in
express-async-handler.
Routers mounted in server.js: /auth, /suppliers, /buyers, /articles, /ratings,
/reports, /transactions.
Identity model — important: Suppliers and Buyers are two separate collections, not
one User model with roles. authController.login probes Supplier.exists({username})
first and falls back to Buyer. Anything touching identity must handle both models.
On login the backend issues three tokens: a 15-minute accessToken, a 1-day refreshToken
set as an httpOnly jwt cookie, and a 30-day longLivedToken for "remember me".
middleware/verifyJWT.js decodes the payload and sets req.supplierId or req.buyerId.
Route protection: /reports and /ratings require a token on every request. On
/suppliers, /buyers, /articles and /transactions, all writes (POST, PATCH,
DELETE) require a token while reads stay public, so the catalogue is browsable without a
session. Per-record ownership is not yet enforced — see Known issues.
Frontend state is split across three mechanisms. Match whichever the file you are editing already uses:
- Context +
useReducerper domain —contexts/{Auth,Suppliers,Buyers,Articles,Ratings}Context.js, consumed through matchinghooks/use*Context.js. This is the primary pattern. - Redux (
store.js,actions/articleActions.js,reducers/articleReducers.js) — used only by the article list / details / review-create flow. ContextProvider.js— UI-only state (theme, sidebar, popups), read viauseStateContext.
CORS is restricted by backend/config/allowedOrigins.js; only http://localhost:3000 is
relevant locally.
Verified from package.json and actual imports.
| Layer | Technology |
|---|---|
| Frontend | React 17, Create React App 5, react-router-dom 6, Tailwind CSS 3, Syncfusion EJ2 19.4, react-bootstrap, react-icons |
| Frontend state | Redux + react-redux + redux-thunk (articles flow only), React Context + useReducer |
| HTTP client | fetch (most pages/hooks) and axios (Redux article actions) |
| Backend | Node.js, Express 4, express-async-handler, helmet, morgan, cors, express-rate-limit |
| Database | MongoDB via Mongoose 7 |
| Auth | jsonwebtoken (hand-rolled), bcrypt, otp-generator |
| Nodemailer over Gmail SMTP | |
| CI | GitHub Actions (.github/workflows/ci.yml) — install, lint, build |
| Tests / Deployment | None present in this repository |
Seven unused backend dependencies were removed during the repository audit: passport,
passport-local, express-session (auth is hand-rolled JWT), mongodb (redundant beside
Mongoose), react-hot-toast (a React library in a backend), and the npm packages crypto
and path, which shadowed the Node built-ins that the code actually resolves to.
Still installed but unused: multer (storage is configured in server.js but never attached
to a route, and its backend/public/assets destination does not exist) and validator. Both
were left in place in case file upload is planned — see docs/AUDIT.md.
.
├── backend/ Express + MongoDB API (port 8000)
│ ├── server.js Middleware wiring and router mounting
│ ├── config/ allowedOrigins, corsOptions, dbConn, mailer
│ ├── routes/ One router per resource
│ ├── controllers/ Request handlers (express-async-handler)
│ ├── models/ Supplier, Buyer, Article, Rating, Transaction
│ ├── middleware/ errorHandler, logger, loginLimiter, verifyJWT
│ ├── views/ Static 404 / index pages (unrelated to the SPA)
│ └── .env.example
├── frontend/ React SPA (port 3000)
│ ├── src/
│ │ ├── config/api.js API base URL (REACT_APP_API_URL)
│ │ ├── contexts/ hooks/ Per-domain Context + useReducer state
│ │ ├── actions/ reducers/ constants/ Redux, articles only
│ │ ├── components/ Shared UI (partly dashboard-template carryover)
│ │ ├── pages/ Screens — see note below
│ │ └── data/dummy.js Template sample data + sidebar links and grid configs
│ └── .env.example
├── .github/workflows/ci.yml CI: install, lint, build for both apps
├── docs/
│ ├── AUDIT.md Repository audit, ambiguities, technical debt
│ └── assets/relay-logo.png Project logo used in this README
├── LICENSE MIT (Syncfusion components are separately licensed)
└── README.md
Domain code vs. template carryover. The frontend shell (Sidebar, Navbar, ThemeSettings,
data/dummy.js, Syncfusion charts and Kanban) comes from a third-party dashboard template.
Real domain work lives in pages/{Suppliers,Buyers,Articles,ArticlesHome,ArticlesList,AddArticle,UpdateSupplier},
all of pages/auth/, and the contexts/ + hooks/ + Redux article files. pages/{Calendar,Kanban,Ecommerce}
and components/{Cart,Chat,Notification,UserProfile,Charts/*} are unmodified template code
rendering hardcoded sample data — they are still routed and reachable, but are not part of
the domain.
- Supplier, Buyer — parallel, independent collections
- Article — belongs to a supplier; embeds a
reviewssubdocument array plus denormalizedrating/numReviews - Rating — standalone supplier↔buyer rating, separate from article reviews
- Transaction — buyer/supplier refs with
amountSpent,deadlineMet,qualityOfService; the sole input toPOST /reports/generate-report
- Node.js 16+ and npm
- A running MongoDB instance (local or Atlas)
- SMTP credentials — the mailer is configured for Gmail, so an App Password is required
# Backend
cd backend
npm install
cp .env.example .env # then fill in real values
# Frontend
cd ../frontend
npm install
cp .env.example .env| Variable | Purpose |
|---|---|
PORT |
Intended API port. Currently ignored — see Known issues. |
MONGO_URI |
MongoDB connection string |
ACCESS_TOKEN_SECRET |
Signs the 15-minute access token and the 30-day long-lived token |
REFRESH_TOKEN_SECRET |
Signs the 1-day refresh token stored in the jwt cookie |
SMTP_USER |
SMTP account used by config/mailer.js |
SMTP_PASS |
SMTP app password |
SMTP_FROM |
Sender address on confirmation and password-reset emails |
| Variable | Purpose |
|---|---|
REACT_APP_API_URL |
Backend base URL. Defaults to http://localhost:8000 if unset. |
ESLINT_NO_DEV_ERRORS |
Keeps ESLint warnings from failing the dev server |
Never commit a real .env; both are gitignored.
Two terminals:
# Terminal 1 — API on http://localhost:8000
cd backend
npm run dev # nodemon; `npm start` runs it without watch
# Terminal 2 — SPA on http://localhost:3000
cd frontend
npm startThe frontend expects the backend to be reachable at REACT_APP_API_URL, and the backend
only accepts browser origins listed in config/allowedOrigins.js.
There are currently no tests in this repository.
frontend:npm testis wired toreact-scripts test(Jest), but no test files exist and no@testing-library/*package is installed, so the command has nothing to run. Adding tests requires installing the testing-library dependencies first.backend: no test script and no test framework.- Linting:
cd frontend && npm run lint(ornpm run lint:fixto auto-correct). This usesfrontend/.eslintrc.js(airbnb, with formatting rules relaxed to warnings). Note thatpackage.jsonalso carries a CRAeslintConfigblock; the two do not fully agree. - No Prettier config and no type checking (plain JavaScript, no TypeScript).
cd frontend
npm run build # production bundle in frontend/build/The build passes. CRA 5 runs eslint-webpack-plugin against frontend/.eslintrc.js during
the build; the pure-formatting rules there (semi, indent, quotes, react-in-jsx-scope
and similar) are set to warn rather than error, because the existing source predates that
config and produces ~1,380 style violations. Correctness rules — no-unused-vars, the React
hooks rules, accessibility — remain errors and will fail the build.
To adopt the strict airbnb style later, run npm run lint:fix and raise those rules back to
error in .eslintrc.js. That reformats nearly every file, so it is best done as its own
isolated commit.
The backend is plain Node.js and has no build step — it is run directly with node server.
No deployment configuration exists in this repository. There are no Dockerfiles, no
docker-compose, and no cloud/infrastructure manifests. .github/workflows/ci.yml runs
continuous integration only (install, lint, build) — it does not deploy anywhere. The
application has only ever been configured to run locally.
Anything required for a real deployment — process management, a production MongoDB, HTTPS,
secret management, and replacing the hardcoded http://localhost:3000 links in confirmation
and password-reset emails — still has to be designed.
Only local/development exists today. There is no test, staging, or production
configuration, and no mechanism for per-environment settings beyond the two .env files.
These are verified against the code and left unchanged deliberately; fixing them changes
runtime behaviour and needs an owner decision. See docs/AUDIT.md for the full list.
PORTfrom.envis ignored.server.jsevaluatesprocess.env.PORT || 8000beforedotenv.config()runs, so the server always binds 8000./suppliers/bydomainand/suppliers/filterare unreachable./:supplierIdis declared first inroutes/supplierRoutes.js, so the param route swallows both literal paths.- No per-record ownership checks. Write routes now require a valid token, but any authenticated user can still modify records they do not own. Reads remain public.
- Password reset does not actually reset a password. The frontend now calls the correct
endpoint with the payload the handler expects, but
authController.resetPasswordlooks the account up by email, generates a new reset token and emails another reset link — it never writesnewPassword. The flow reaches the server and returns 201, but the password is unchanged. Fixing this is a business-logic decision; seedocs/AUDIT.md. - A non-"remember me" session does not survive a reload.
useLoginwrites to thelocalStoragekeyuser, butApp.jsrehydrates only fromrememberMe. - Email links are hardcoded to
http://localhost:3000inauthController.js. multeruploads would fail.server.jsconfigures an upload destination ofbackend/public/assets, andbackend/public/does not exist. Theuploadmiddleware is never attached to a route, so this is currently inert.craco.config.jsis dead.@craco/cracois not installed and the scripts callreact-scriptsdirectly; Tailwind runs through CRA's own PostCSS. Editing the craco config has no effect./barthrows.App.jsrenderspages/Charts/Bar.jsxwithout thecompareDataprop it calls.flatMap()on.- Syncfusion runs unlicensed (no
registerLicensecall), so its components render trial watermarks.
- Backend starts but nothing responds / no data.
config/dbConn.jscatches connection errors with a bareconsole.logand lets the process continue, so a badMONGO_URIlooks like a silent failure rather than a crash. Check the startup output for a Mongo error. - Frontend requests fail with a CORS error. Confirm you are browsing on
http://localhost:3000; other origins are rejected byconfig/allowedOrigins.js. - Registration succeeds but no email arrives. Verify
SMTP_USER/SMTP_PASSinbackend/.env. Gmail requires an App Password, not the account password. - Confirmation link does not work. See the route-mismatch entry under Known issues.
- Changing the backend host. Set
REACT_APP_API_URLinfrontend/.envand restart the dev server — CRA reads.envonly at startup.
Built with ❤️ by Hamdi Khsib
