INTRO
Role-based UIs look like “hide a menu item” until you debug a ticket where an operator could see a status the API never allowed, or an admin action skipped audit. The fix is almost always explicit rules in one place - not scattered `if (role === ...)` checks.
Make permissions explicit in API and UI
The UI should reflect what the API enforces, not the other way around. I keep a small matrix (role × action × resource) or capability strings the server checks on every mutating route.
Duplicating role logic in React and Express guarantees drift. Either share a module (monorepo), code-generate constants, or treat the server as the source of truth and derive UI flags from a `/me` payload.
code-snippet.typescript
function requireCapability(cap: string) {
return (req: AuthedRequest, res: Response, next: NextFunction) => {
if (!req.user?.capabilities?.includes(cap)) {
return res.status(403).json({ error: 'Forbidden' })
}
next()
}
}
router.post(
'/tickets/:id/close',
requireCapability('ticket:close'),
closeTicketHandler
)Workflow clarity beats extra UI chrome
Operational tools fail when people cannot answer: what state is this in, what can I do next, and who else is affected? Extra badges rarely fix that; a tight state model does.
I document allowed transitions (e.g. open → in_progress → resolved) and reject invalid jumps at the API. The UI simply offers valid actions.
Keep inventory and ticket updates consistent
When a ticket implies a stock movement, either wrap both updates in a transaction (where the datastore allows) or design compensating events you can replay. Half-updated rows are worse than a slow page.
For MongoDB, embedding vs referencing depends on update frequency. High-churn lines often belong in their own collection with references, so you do not rewrite huge documents on every status change.
Maintainability is delivery speed six months later
Feature teams measure “done” at merge. Operators measure “done” when Tuesday’s edge case still works. Folder-by-feature routes, typed DTOs, and repeatable list/detail patterns pay off when the client asks for one more role.
I avoid “smart” generic components that encode business rules in props. Those become prop-drilling nightmares. Prefer boring pages composed of dumb UI + thin hooks.
KEY TAKEAWAYS
“Enforce permissions on the server; mirror results in the client.”
- 02
Model workflows as explicit states and transitions, not ad-hoc flags.
- 03
Treat cross-entity updates (inventory + ticket) as consistency problems.
- 04
Structure code by feature so role additions do not scatter changes.
CONCLUSION
Closing thought
Role-based MERN apps punish implicit rules. If you name capabilities, model states, and keep mutations honest at the API, the React layer stays understandable — and you ship the next workflow change without fear. Building SaaS roles next? Read the RBAC guide, then Contact.
CONTINUE
Related pages
UP NEXT
Next.js Environment Variables and Secrets in Production
A checklist for App Router apps: what belongs in `NEXT_PUBLIC_`, what must never ship to the client, and how runtime config differs from build-time `process.env` inlining.
Read next article