Skip to content

πŸ—οΈ Architecture ​

Our architecture is grounded in the 15-factor app methodology β€” an evolution of the original 12-factor app (Heroku) extended by Kevin Hoffman. Each factor is a design principle that keeps apps scalable, portable, and maintainable.

Below is each factor mapped to our stack. Factors with a dedicated page go deeper β€” this is the overview.


I Β· Codebase ​

One codebase, many deploys.

Our monorepo (tell-ia-solutions) is the single source of truth, deployed to main, staging, and production. Anything that affects how the system behaves lives here β€” no behaviour in dashboards, no logic in Notion. β†’ Code-driven

II Β· Dependencies ​

Explicitly declared, never implicit.

pnpm workspaces with workspace:* protocol. No global installs assumed.

III Β· Config ​

Config in the environment, never in code.

Backend uses Zod-validated config factories β€” typed, validated at startup, throws on misconfiguration. β†’ Configuration

IV Β· Backing services ​

Treat databases, queues, and external APIs as attached resources β€” swappable via config.

MongoDB, Redis, and Firebase Auth are all referenced by connection string. The same services run locally in Docker, in CI via Testcontainers, and in production β€” only the connection string changes. β†’ Configuration Β· Integration Testing

V Β· Build, release, run ​

Strictly separate build and run stages β€” never mutate a running deploy.

Turborepo handles the build pipeline, respecting dependency order across packages. CI produces the artifact; Railway handles release and run. β†’ Deployment

VI Β· Processes ​

Stateless, share-nothing processes.

β†’ Stateless Services

VII Β· Port binding ​

Services are self-contained and expose themselves via a port β€” no app server required.

NestJS binds to a configured port and is complete on its own. A useful side effect: multiple isolated instances can run on the same host, which is exactly how Testcontainers runs MongoDB, Redis, and the Firebase emulator in parallel during tests. β†’ Integration Testing

VIII Β· Concurrency ​

Scale out via the process model, not by making processes bigger.

Long-running, error-prone operations run in BullMQ workers β€” independently scalable from the main process. β†’ Queues & Async

IX Β· Disposability ​

Fast startup, graceful shutdown.

NestJS lifecycle hooks handle teardown β€” open connections are closed, in-flight work is finished. BullMQ workers drain their current jobs before stopping, so no task is lost mid-execution. Fast startup matters too: Railway deploys a new instance before tearing down the old one, so a slow boot directly affects downtime.

X Β· Dev/prod parity ​

Keep environments as similar as possible β€” the further dev drifts from prod, the more surprises at deploy time.

Local stack mirrors production: same MongoDB version, same Redis, same Firebase Auth via Testcontainers. Staging and production run on the same Railway setup with the same configuration shape. The target is fully declarative infrastructure (Terraform + Docker) so the environment definition is checked into the repo and identical between staging and prod. β†’ Infrastructure

XI Β· Logs ​

Treat logs as event streams, never manage log files.

Pino with ECS format, shipped to the observability stack. β†’ Logging

XII Β· Admin processes ​

Run admin tasks as one-off processes, not baked into the app.

β†’ Bootstrap jobs

XIII Β· API first ​

Design the contract before the implementation.

Zod schemas in @tellia-solutions/schemas are the contract β€” shared between frontend and backend, validated at runtime. β†’ Schemas & Records

XIV Β· Telemetry ​

Observability is not optional.

Structured logs, APM traces, and dashboards are first-class. β†’ Observability

XV Β· Authentication & Authorization ​

Security built-in, not bolted on.

Firebase Auth + CompanyAuthGuard on every protected route. Multi-tenancy enforced at the service layer. β†’ Auth & Identity