Case study 01 · Enterprise SaaS

RepairCMS

A multi-tenant repair-management platform that 1,100+ repair and service businesses across nine markets run their daily operations on. CRM, inventory, point of sale, subscriptions, cashbook and a dynamic form builder live in one system, with billing and accounting wired to the tools the business already used.

Client
Candy Melon Software GmbH, Austria
Role
Lead architect and engineer
Timeline
Mar 2025 to now
RepairCMS inventory item screen: stock level, sales and purchase price, and supplier pricing

01The problem

Repair shops run on a spread of disconnected tools: a spreadsheet for stock, a separate till, a form service for intake, an accountant's software that never matches the till, and email for everything in between. RepairCMS exists to hold all of that in one tenant-isolated system, so a shop owner sees one set of numbers rather than four.

I joined in March 2025 to ship features and was promoted to senior engineer in February 2026; the platform is now mine to shape. The architecture is mine to own: module boundaries, tenant isolation, the API contract between the NestJS backend and the React client. So are the parts nobody wants to inherit, like invoicing, VAT and receipt numbering.

02Architecture

A NestJS backend split into domain modules, each with its own controllers, services, DTOs and Mongoose schemas, over MongoDB, with a React client consuming it through RTK Query. Every service scopes its queries to the shop through one shared helper, and tenant-scope regression tests guard the routes that change data.

CRM
Customers, devices and repair history, scoped per tenant.
POS
Counter sales and fiscal receipts at the till.
Inventory
Parts and stock movements tied to the repairs that consume them.
Cashbook
Daily cash reconciliation that matches the till and the books.
Quoting
Quotes that carry their tax and rounding rules into the invoice they become.
Form builder
Tenant-defined intake forms rendered from stored schema, with Typeform ingestion.
Customer portal
Where the shop's own customers follow repairs and documents.
Stripe webhook: each event handled onceStripe eventverify signatureclaim event idunique indexrun handlerdoneduplicate: skippedhandler fails: claim released, Stripe retriesLexware sync: each invoice booked onceinvoice issuedcreate voucherOAuth 2.0 + PKCEvoucher id back?yes: syncedno: failedretry offereda retry finds the voucher by invoice number: no duplicate
RepairCMS billing pipeline, simplified from the code

Around the core: Stripe for subscription billing and webhooks, Lexware for accounting sync, Typeform for intake that lands as structured records, AWS SES and Sender for transactional and marketing mail, S3 for documents and media, EC2 behind Nginx with PM2 for process management, and GitHub Actions for deploys.

03Decisions

  1. 01

    Tenant scoping as an explicit, tested rule

    Every service resolves the shop from the logged-in user through one shared helper and adds it to the query; reading tenant data by id alone is against the rule. Tenant-scope regression tests cover the routes that change data, and I lead the work bringing older modules up to the same rule.

    Trade-off It is a convention, not a framework guarantee: a forgotten filter returns every shop's data, so it is held by tests and review rather than by the database layer.

  2. 02

    Money logic gets its own review rules

    Invoices, quotes, VAT calculation, rounding, receipt numbering and payment webhooks are treated as a separate risk class with their own checks before merge. A rounding error in a customer's books costs more to repair than any UI bug.

    Trade-off Slower changes in exactly the area the business most often wants changed quickly.

  3. 03

    RTK Query as the single client cache

    All server state lives in RTK Query with explicit cache tags, so a mutation invalidates precisely what it should and lists stay honest. Legacy local state was migrated out rather than kept in parallel.

    Trade-off Tag discipline is now a real convention the team has to hold; a missed tag shows up as stale UI.

  4. 04

    Forms as stored schema, not code

    Shops needed different intake questions per store and per device type. Rather than shipping a release per request, the form builder stores the schema and the client renders it, with Typeform feeding the same pipeline.

    Trade-off Validation and versioning of stored schemas became a problem to solve properly rather than a compile-time guarantee.

04Proof

1,100+
Businesses running on the platform
9
Markets it operates in
~35%
Faster APIs on high-traffic endpoints (internal measurement)
20+ h
Bookkeeping saved per tenant, monthly (customer-reported)

NextTalk

Want this level of ownership on your platform?

Thirty minutes is usually enough to tell you whether your architecture is the problem.

Md Shoharab Pk, Senior Full-Stack Engineerdevshoharab@gmail.com