The problem
Teams were paying for a stack of disconnected tools — one for issues, one for documents, one for standups, another for credentials — none of which shared an identity model or a permission model. WrkOS consolidates them into a single product without collapsing into a monolith that nobody can safely change.
Structured as a monorepo, not a monolith
The codebase is a Turborepo workspace with two applications sharing roughly fifteen packages. Each product area is its own feature package with its own boundary, so work on one area does not destabilise the others.
- Feature packages for agile, calendar, CMS, docs, standups and a password vault, each independently owned.
- Shared packages for the design system, API client, state store and types, so a change to a contract surfaces at compile time across every consumer.
- Turborepo task caching, so a change inside one feature does not trigger a rebuild of the entire graph.
Real-time collaboration with CRDTs
The document editor uses Yjs rather than operational transforms. Conflict-free replicated data types converge without a central authority arbitrating edit order, which means two people typing in the same paragraph merge deterministically, and a client that drops offline reconciles cleanly when it reconnects instead of losing work. Documents sync over a y-websocket server that runs alongside the NestJS API.
Cycles, standups and billing
I worked across the agile cycle module — the sprint model that issues are planned into — the standup feature, and the subscription and billing surface, spanning the Redux store slices on the client and the corresponding NestJS modules on the server.
- Cycle planning and rollover on the backend, exposed through a documented Swagger surface.
- Standup collection with a scheduled job driving the prompt cadence.
- Subscription state modelled as its own store slice and backend module, keeping plan and entitlement logic out of feature code.
- Settings and account management screens in the core application.
The API
A NestJS backend using Prisma against a relational schema, with WebSocket gateways for live updates, an event emitter for cross-module side effects, a scheduler for recurring work and a microservice transport for jobs that should not run in the request path. Auth runs through Passport with JWTs, uploads go to S3, and the whole thing is containerised with Docker Compose for local development.