Skip to content

ARCHITECTURE

Boring technology, arranged carefully.

Nothing here is exotic. TypeScript, Fastify, PostgreSQL, Prisma, Redis. The value isn't in the choices — it's in the discipline about where code is allowed to live and what's allowed to be slow.

TWO PROCESSES, ONE BODY OF LOGICAPI serveranswers in millisecondsBackground workertakes as long as it takesthe same domain logicso a rule is never implemented twicePostgreSQLthe truthRedisthe queueS3-compatiblethe filesStateless. Add another API server and nothing in this picture changes.

The stack

Every one of these is checkable, which is the point of naming them.

  1. Language

    TypeScript

    Whole categories of mistake are caught while writing the code, not while a customer is using it.

  2. Web framework

    Fastify

    One of the fastest available, without needing special tricks to get there.

  3. Database

    PostgreSQL

    Mature and relational, with serious transaction guarantees, real data integrity, and advanced types when you need them.

  4. Data access

    Prisma

    Type safety between the code and the database, plus migrations that are tracked and reversible.

  5. Queue & background work

    BullMQ on Redis

    Anything that must not slow down a response: email, image processing, reports, scheduled jobs.

  6. File storage

    S3-compatible

    Media scales without bounds and without touching the application server.

  7. API documentation

    OpenAPI / Swagger

    Generated from the same definitions the code uses, so the docs can't drift from reality.

Every request takes the same path.

Layered by rule rather than by convention — which is why what a layer may NOT do is written down beside what it does.

  1. Route

    Input validation and authentication. No business logic.

  2. Controller

    Translates an HTTP request into an internal call and shapes the response. No business logic.

  3. Service

    All business logic lives here. Rules, calculations, decisions. Nowhere else.

  4. Repository

    The only layer permitted to talk to the database.

  5. Database

    Reached through the repository and nowhere else.

The separation means each layer is testable on its own, a change in one doesn't leak into the others, and new code always has an obvious home. That last point is what keeps the cost of maintenance from compounding in year two.

Two processes

API server

Answers user requests. Stays fast because it refuses to do heavy work — anything expensive is handed off.

Background worker

Does everything that might take seconds or minutes: sending email, processing media, generating reports, running translations, regenerating image derivatives, firing scheduled reminders.

They share the same domain logic, so a rule is never implemented twice. The split means one operator exporting thousands of orders never slows down a customer mid-checkout.

Every endpoint answers the same way.

Success or failure, the response has exactly one shape.

Every client that connects — web, mobile, a future GraphQL layer — speaks one consistent language for handling errors, and never gets surprised. The envelope has three parts:

A stable machine-readable code

It identifies the failure and does not change when the wording does, so client code can branch on it safely.

A human-readable message

In the right language, ready to show a person.

Structured detail

Which field, and what was wrong with it — not a sentence a client has to parse.

The literal field names and an example of each response live in the generated API reference, where they are produced from the same definitions the code uses and cannot drift from it.

Built to grow without a rewrite.

Stateless servers

No session data is kept on the server, so adding more servers under load is straightforward and needs no code change.

Heavy work is always background work

Anything that could take seconds leaves the response path entirely.

Measured latency budgets

Every endpoint has a millisecond target, tested with a real load tool rather than estimated.

The right order of optimisation

Database indexes first, then inefficient queries, then payload size. Caching is the last resort, always with an explicit key, lifetime, and invalidation strategy — never used to hide a real problem.

Media uploads bypass the server

Files go straight to cloud storage. Application resources are never spent shifting bytes.

Designed for years of data

Change history, financial documents, and operational logs each have their own retention policy from the start, so operational data stays controlled while commercial and legal records are never lost.

One organisation today, many tomorrow.

The data model was designed so a single-organisation installation can become a multi-organisation platform — a multi-vendor marketplace, for instance — without redesign. Organisations are a first-class concept, and data is scoped at the authorisation layer rather than by hoping every query remembers to filter.

See how a marketplace uses it
Org AOrg BOrg Cauthorisation layer — scoping decided here, onceone data modelNot by hoping every query remembers to filter — there is one place to get right.

Want to go deeper than a marketing page?

Book a technical walkthrough. Bring your hardest question about the data model.

  • We reply within one working day
  • No sales sequence, no drip campaign
  • NDA before the call if you'd prefer