A multi-role field-service operations platform
One service operation, three audiences: customers, field staff and back-office administrators, served by a TypeScript API, a web app and a single Flutter binary.
- Sector
- Field service and operations
- Engagement
- August 2022 – June 2023
- TypeScript
- Node.js
- Express
- MongoDB
- React
- Flutter
- Firebase
Context
A service business that dispatches people to sites has three populations using one system at once. Customers request and track work. Field staff receive it, do it and report on it. Administrators price it, assign it and reconcile it afterwards. They need the same records and almost none of the same screens.
The usual answer is three separate applications and three separate release cycles. The answer here was one API, one web application for the desk-based roles, and one mobile binary that opens into a different experience depending on who signs in — plus the ability to switch which outlet you are working against, because the operation runs from more than one location.
What we built
REST API
A TypeScript service on MongoDB with an explicitly versioned route tree, separated into controllers, services, models, middleware, error handling, notification templates and shared types.
Web app
The desk-based surface, covering administration, authentication and the operational views, with outlet switching built in as a first-class control rather than a settings page.
Mobile app
One Flutter binary containing authentication plus three separate role areas, structured into controller, repository and widget layers.
Architecture
The route tree is versioned at the top level, so a breaking change to one client can ship without stranding the other two. On a platform where a mobile release reaches devices days after a server deploy, that is the difference between shipping and waiting for app-store review.
This is a TypeScript backend, which was not the default for this kind of build at the time. It is the one API in this group of platform engagements that is typed end to end, and the payoff is exactly where a three-audience system hurts most: one set of shared types describing what each role is allowed to see.
Role separation lives inside a single mobile binary rather than across three published apps. One build to sign, one release to review, one crash-reporting stream — and a field engineer who is promoted to supervisor does not have to install anything.
Push delivery and media handling are pushed out to managed services rather than built, which is the right trade for an operations tool whose value is in dispatch logic, not in re-implementing notification infrastructure.
Stack
- API
- TypeScript
- Node.js
- Express 4
- MongoDB via Mongoose 6
- JSON Web Tokens
- bcrypt password hashing
- Web
- React
- Framer Motion
- Mobile
- Flutter
- Dart
- GetX state management
- Dio HTTP client
- Local preference storage
- Infrastructure
- Firebase Admin SDK for push
- AWS SDK for media
- Nodemailer for transactional mail
Scale
Counts that can be re-derived from the same repositories. Nothing here is a business result — a repository cannot evidence one.
- Three delivered surfaces: API, web app, mobile app.
- 1,180 commits across those three repositories — 413 on the API, 644 on the web app, 122 on the mobile app.
- Three distinct role experiences inside one mobile binary.
- Roughly ten months from first commit to last.
Where the evidence stops
- A fourth repository exists for a super-admin surface, but it was never built: its default branch holds a README and one commit. Three surfaces were delivered, not four.
- The repositories carry no project documentation beyond a default template, so the domain here is read from module structure. That is why this page describes the system's shape rather than the operator's business.
- The mobile repository is large, but the weight is bundled media. It is not a measure of how much code was written.
- No business result is recorded in the repositories, so none is claimed on this page.
Evidence
Each statement above, and the artefact it was read from. The client cannot be named, so the sources are given instead.
Three surfaces were delivered; a fourth was scoped but never built.
SourceRepository trees for all four. The super-admin repository's default branch contains only `README.md`, at one commit.
The API is TypeScript end to end.
SourceGitHub languages API reports TypeScript at 97% for the API repository; the tree carries `tsconfig.json` and a dedicated `types/` directory.
The API route tree is explicitly version-namespaced.
SourceA `v1` directory inside the API repository's `src/routes/`.
The API is separated by concern, not by feature folder.
SourceSibling directories in the API's `src/`: controllers, services, model, middlewares, error handling, templates and types.
One mobile binary carries authentication plus three distinct role areas.
SourceThe mobile repository's `lib/screen/` holds four sibling directories: one for authentication and three for separate roles.
Outlet switching is a first-class control in the web app.
SourceA dedicated outlet-switching component sits at the top level of the web app's component directory, alongside header, sidebar and authentication.
Push and media are delegated to managed services.
SourceAPI `package.json` dependencies: `firebase-admin@^11.2.0`, `aws-sdk@^2.1223.0`, `nodemailer`.
1,180 commits across the three real repositories.
SourceSum of `history.totalCount` per default branch, GitHub GraphQL API: 413, 644 and 122.
Related capability