The problem
A new company needed a multi-tenant backend for buildings, doors, and people, with IoT readers that must still decide access when the network is unreliable.
System architecture
flowchart LR
subgraph control ["Control plane"]
control_0["NestJS APIs"]
control_1["Tenant and permission service"]
control_2[("PostgreSQL transactional data")]
end
subgraph devices ["Devices"]
devices_0(["Smart lock / reader"])
devices_1["Permission snapshot on device"]
devices_2["Idempotent door command handler"]
end
subgraph flex ["Flexible payloads"]
flex_0[("MongoDB device documents")]
end
control_0 --> control_1
control_1 --> control_2
control_1 --> devices_2
devices_2 --> devices_0
control_1 --> devices_1
devices_0 -->|"telemetry"| flex_0Key architecture decisions
Server is source of truth; device holds a snapshot
Access decisions are computed on the server; devices keep a permission snapshot for offline intervals.
Operators cannot revoke access in real time if the device only trusts its last full sync forever.
Rejected Device-only ACL with no expiry — rejected for security review on building egress.
Trade-off Snapshot TTL and reconciliation when the device comes back online.
Idempotent door commands
Every open command carries a unique command ID, so a retry after a timeout is recognised as the same command and cannot create a second physical open.
Mobile and gateway retries are normal; actuators are not.
Rejected Fire-and-forget HTTP to lock firmware — rejected after duplicate-open reports in testing.
Trade-off Command store or cache to remember accepted keys for a window.
PostgreSQL plus MongoDB by shape, not hype
PostgreSQL for tenant, people, doors, and transactional permissions; MongoDB for variable device payload documents.
Relational invariants for who may open what; flexible documents for vendor-specific telemetry.
Rejected Mongo-only for everything — rejected when relational reporting was required week one.
Trade-off Two stores to backup and monitor; clear ownership of which API touches which store.
What I own, wrote, and delegate
Own
- CI/CD, review rules, branching, and initial backend architecture
- Pivot plan that kept tenant, auth, and service boundaries
Wrote
- NestJS multi-tenant APIs and permission model for access decisions
- Onboarding path: one vertical slice per new engineer in week one
Delegate
- Mobile UI after API contracts were stable
- Chain integration on the company's first product
Scale
- Initial engineering team of 9 across backend, frontend, and mobile
- Concurrent with InnoVerse from 08/2024; InnoVerse became the primary role
- The smart office platform was still in production when I last had visibility
Boundaries
- The two-month release was the first product, a blockchain real-estate platform. This case is the access-control backend after the pivot, on the same platform.
- Reader protocol, and fail-safe versus fail-secure, are chosen per door at the site.
What we watch in operation
Rules the design never breaks
- No command is sent without a resolved tenant and permission check on the server.
- Pivot removes domain tables, not tenant or identity modules.
- Mandatory review before merge to the release branch.
Broader application
- Greenfield teams that need structure before they need features.
- Access control where offline devices still need a predictable policy.
- Product pivots that must reuse security foundations.