Case study 08 · Marketplace and revenue
Five kinds of user in one codebase, separated by route
Service vendors, fleet carriers, owner operators, company drivers and a call center each had their own dashboard, with permissions set by user type and role.
506 routes
the headline number
1 constraint
Five kinds of user did different work on the platform, but every payment, branding or security change had to reach all five.
The story in 5 steps
Stack
- React
- Redux
- Node.js
- Express
- MySQL
01 · Context
The platform served five kinds of user who did different work on it.
The platform served five kinds of user who did different work on it. A vendor managed listings, invoices and renewals. A driver had recent searches, preferred vendors, call logs, trucks and repair tickets. A call center operator worked on behalf of other users.
One codebase served all five, and the company's own team used a separate internal CRM.
02 · Could not change
Building five separate applications looks cleaner, and for about six months it is faster.
Building five separate applications looks cleaner, and for about six months it is faster.
With five applications, every shared change, such as a payment flow, a branding update or a security fix, has to be made five times. It will be made four times, and nobody will notice which one was missed until a customer does.
A single application with role conditions scattered through the interface is the opposite mistake, and just as common. That version cannot be audited, because the only way to answer "what can a driver actually do?" is to read the entire codebase looking for conditionals.
03 · What I did
I separated the user types by route. Each type had its own set of routes under /dashboard/{user_type}/{page}, so the vendor's routes showed everything a vendor could do.
I separated the user types by route. Each type had its own set of routes under /dashboard/{user_type}/{page}, so the vendor's routes showed everything a vendor could do.
Permissions resolved as user type plus role. Every user type had at least two roles, such as admin, owner and operator, and a permission depended on the combination. A vendor's operator was a different subject from a fleet carrier's operator, even though both were called "operator."
The backend checked permissions too. Separate routes in the client only organized the code, so the API checked every permission independently. A rule enforced only in the interface can be bypassed by anyone who calls the API directly.
I split the API in two. Public routes and admin routes were two separate sets instead of one set with checks inside. The internal CRM was, in effect, a separate product with its own API surface.
04 · Result
Five user types and their roles served from one codebase
- Five user types and their roles served from one codebase
- 506 API routes in two separate sets
- Permissions that could be audited by reading one set of routes
05 · Differently
The permission model existed in the routing and in the API, but it was never written down anywhere a person could read it as a whole.
The permission model existed in the routing and in the API, but it was never written down anywhere a person could read it as a whole. Each new developer had it explained verbally, and verbal explanations of an authorization model drift. I should have generated a single table of user types against actions. It would have taken a day and answered the question for good.
Related services
The services that draw on this work.
- Web developmentFull-stack web developmentA marketplace codebase shared by five kinds of user, with permissions set by type and role. The API repeats every check the interface makes.
- Web developmentMulti-tenant and white-label SaaSOn a truck-services marketplace, one codebase served five kinds of user: service vendors, fleet carriers, owner operators, company drivers and call center staff. A permission combines type and role, so a vendor’s operator and a fleet carrier’s operator have different rights.
- Web developmentMarketplace and logistics platformsA US marketplace for truck services, where each user type has its own routes and what a user may do depends on both their type and their role. The 506 API routes are split into two separate sets, public and admin.
- Web developmentAdmin dashboards and internal toolsOn the same platform, a separate CRM for the company’s own staff. It talks only to the admin set of API routes, which is kept apart from the routes customers use.
- Web developmentReact SPA architectureReact and Redux dashboards for five kinds of user on a US marketplace. Each user type got its own branch of the router, /dashboard/{user_type}/{page}, so role checks didn’t spread through the components.
- Backend, data and integrationsAPI and backend development506 API routes for a US marketplace with five kinds of user, kept in two separate sets for public and admin use. The API checks permissions itself and leaves none of that to the interface.
- Backend, data and integrationsPermissions and RBACOn a US marketplace, five kinds of user share one codebase and each has at least two roles. A permission is resolved from type and role together, so an operator at a vendor and an operator at a fleet carrier get different permissions. The API’s 506 routes are divided between a public set and an admin set.
- Regulated and sensitive systemsSecurity reviewFive kinds of user, from service vendors to a call center, share one codebase. Each type has its own routes. Permissions combine type and role, and the API validates them itself instead of trusting the route a request came from.
- AISupabase reviewOne codebase for five kinds of user, where a vendor’s operator and a fleet carrier’s operator get different rights because a permission is resolved from user type and role together. The backend validates it again, since separating routes in the client only organizes the interface.
Case study 08
Working on a similar problem? We reply within one business day.
The first call takes 30 minutes, and you’re under no obligation.