Admin panel and HR panel

Locking Down the Project Structure

Started by auditing an early, somewhat scattered folder layout and moved to a clean, modular structure under src/. Each domain — starting with user — got its own controllers, services, and routes, while core/system-level concerns like admin registration live at the root level. Config, database connection, environment validation, and Swagger docs each got dedicated files under src/config/.

Database Setup, the Raw SQL Way

Rather than reaching for an ORM, I stuck with pg and raw SQL — matching the team’s preference for direct control over queries. Schema definitions live in src/db/schemas/, one file per table, starting with user.schema.sql: a single shared users table with a role column (admin, shopkeeper, customer) instead of separate tables per role. Email is nullable since customers don’t provide one; phone is required and unique across everyone.

To keep schema changes tracked and repeatable, I brought in node-pg-migrate — no more manual, untracked SQL runs in DBeaver. Every schema change now becomes a version-controlled migration file with proper up/down functions.

Authentication: Registration and Login

I built out the full auth flow:

  • Shopkeeper registration — full name, email, phone, password.
  • Customer registration — full name, phone, password, no email required.
  • Admin registration — gated behind a server-side secret key, not open signup, since admin accounts shouldn’t be self-served.
  • Shared login — one endpoint for all three roles, accepting either email or phone plus password, returning the user’s role and a JWT so the frontend knows exactly where to route them.

Passwords are hashed with bcrypt, credentials are checked with a single generic error message (no leaking whether the email or password was wrong), and the JWT secret is validated at startup so misconfiguration fails fast instead of surfacing as a confusing runtime error later.

API Docs and Testing

Swagger docs got wired up and kept in sync with the real endpoints as they evolved — register/login schemas, request bodies, and response codes all match what’s actually implemented. I also hit and resolved a CORS snag where Swagger UI’s own requests were being blocked, by explicitly allowing both the frontend origin and the API’s own origin.

For day-to-day testing, I settled on documenting each endpoint in plain text (method, URL, body) rather than distributing Bruno collection files, keeping things lightweight and easy to copy-paste.

Cleaning Up Loose Ends

Along the way I fixed a handful of real bugs: broken imports left over from the restructure, an .env path resolution issue that was silently pointing at the wrong directory, and a package.json with dead scripts referencing a folder structure that no longer existed.

Looking Ahead: The Frontend

With the backend’s module-based pattern proven out, I mapped the same approach onto the React frontend — feature folders for user, retailer, and admin, each with their own components, pages, services, and hooks, plus a shared layer for common UI pieces. A handoff summary was written up so frontend work stays aligned with how the backend is organized and how auth is expected to flow.

Author: Muhammad Faisal

Leave a Reply

Your email address will not be published. Required fields are marked *