Back to Blog
6 min read·October 20, 2026

Inside OnDelivery: architecture, stack and system map

How I organized OnDelivery: a monorepo, several services, asynchronous messaging and containers, with the stack by layer and the system map.

EC
Éverton Carvalho
Software Engineer | Software Architect | CTO
Inside OnDelivery: architecture, stack and system map
Related Case Study
OnDelivery: the architecture behind the platform
View ADR

OnDelivery is the platform I build for restaurants and delivery businesses: orders, point of sale, deliveries, catalog, inventory, cash register, coupons, loyalty and an online store for customers, plus apps for waiters, couriers, the kitchen (KDS), kiosks and QR menus.

I organized the code as a monorepo with several services. The backend uses NestJS and Express.js on PostgreSQL, with BullMQ and RabbitMQ for asynchronous processing; the front-ends are built with Next.js and React. Everything runs in containers, with tests and CI on GitHub Actions.

Stack by layer

  • Languages: TypeScript, Go and Python
  • Runtime: Node.js
  • Backend: NestJS, Express.js and WebSocket / Socket.io
  • Frontend: Next.js, React, Tailwind CSS and TanStack Query
  • Data: PostgreSQL with TypeORM, and Redis
  • Messaging: BullMQ and RabbitMQ
  • Validation: Zod
  • Testing: Jest, Vitest and Playwright
  • Delivery: Docker, GitHub Actions and NGINX
  • Observability: OpenTelemetry, Grafana, Prometheus, Loki and Tempo

Architecture

I organized OnDelivery as a monorepo with several workspace packages. Each service is built from its own code and orchestrated with Docker Compose, and asynchronous communication between them goes through BullMQ and RabbitMQ.

The apps, the entry layer, the NestJS services and what each one uses. Arrows show how the parts talk; the services talk to each other over RabbitMQ.

flowchart LR
  subgraph fe["Front-ends"]
    sellers["Dashboard, POS, KDS, waiter and kiosk (Next.js, PWA)"]
    web["Online store (Next.js)"]
    qr["QR menu (Next.js)"]
    native["Waiter, courier and card terminal app (Expo)"]
    desk["Printing app (Electron)"]
  end
  subgraph entry["Entry"]
    bff["Next BFF: HttpOnly cookie"]
    nginx["NGINX"]
  end
  subgraph svc["NestJS services"]
    commerce["commerce: orders, stock, cash register, KDS"]
    auth["auth"]
    payment["payment"]
    subs["subscription"]
    comm["communication: WhatsApp, email, SMS"]
    proc["procurement: invoices"]
    sefaz["sefaz: tax invoice issuer"]
    others["catalog, affiliates and scheduling"]
  end
  subgraph data["Data and messaging"]
    pg[("PostgreSQL: one database per service")]
    mq["RabbitMQ"]
    bull[("Redis: Bull queues")]
    cache[("Redis: cache")]
  end
  subgraph ext["Integrations"]
    paygw["Payment gateways"]
    sefazext["SEFAZ"]
    wa["WhatsApp"]
  end
  obs["Observability: OpenTelemetry and Grafana"]
  sellers -->|REST| bff
  web -->|REST| bff
  bff -->|Bearer| nginx
  qr -->|REST| nginx
  native -->|REST| nginx
  desk -->|REST| nginx
  nginx --> svc
  commerce -.->|SSE| bff
  svc --> mq
  mq -->|printing| desk
  proc -->|REST| sefaz
  sefaz -->|signed XML| sefazext
  commerce --> bull
  comm --> bull
  svc --> pg
  commerce --> cache
  payment --> paygw
  subs --> paygw
  comm --> wa
  svc -.->|OTLP| obs
  class commerce,auth,payment,subs,comm,proc,sefaz,others tech-nestjs
  class pg tech-postgresql
  class mq tech-rabbitmq
  class bull,cache tech-redis
  class nginx tech-nginx

The path of an order: payment is confirmed by webhook, the order shows up in realtime on the dashboard and the KDS, the receipt is printed and the NFC-e is issued.

flowchart LR
  client["Customer in the online store"] -->|checkout| commerce["commerce"]
  commerce -->|payment link| mq["RabbitMQ"]
  mq --> payment["payment"]
  payment --> gw["Payment gateway"]
  gw -->|webhook| payment
  payment -->|order paid| mq
  mq --> commerce
  commerce -.->|SSE| panel["Dashboard and KDS"]
  commerce -->|printing| mq
  mq --> printer["Printing app"]
  commerce -->|issue NFC-e| mq
  mq --> proc["procurement"]
  proc -->|REST| sefaz["sefaz"]
  sefaz -->|signed XML| sefazext["SEFAZ"]
  commerce -->|notice| comm["communication: WhatsApp"]
  class commerce,payment,proc,sefaz,comm tech-nestjs
  class mq tech-rabbitmq

System map

These are OnDelivery's components, grouped by area. I left out addresses, internal URLs and credentials.

  • Entry: nginx and vps-private-bridge
  • Services: commerce, payment, subscription, communication, evolution-api, affiliates, auth, catalog, procurement and sefaz
  • Data: commerce-db, auth-db, scheduling-db, services-db, evolution-service, db, cache (Redis), rabbitmq and Postgres-PITR
  • Observability: grafana, prometheus, loki, tempo, otel-collector, cadvisor and node-exporter

The components of each area. I split the overview into parts to fit small screens; expanding the first one shows the whole map with the links between components.

flowchart TB
  %% kind: system-map
  %% "~~~" lines are invisible layout hints, not links
  accTitle: System map — overview (part 1)
  accDescr: OnDelivery's components grouped by area.
  subgraph aEntry["Entry"]
    direction TB
    m1["     nginx                      (proxy · NGINX)"]
    m2["vps‑private‑    bridge      (bridge)  "]
    m1 ~~~ m2
  end
  subgraph aServices["Services"]
    direction TB
    m3["commerce            (service)  NestJS  "]
    m4[" payment            (service)  NestJS  "]
    m5["subscription               (service)      NestJS   "]
    m6["communication                 (service)      NestJS    "]
    m7["evolution‑api   (service)  "]
    m8["affiliates   (service) "]
    m9["   auth      (service) "]
    m10["  catalog    (service) "]
    m11["procurement  (service) "]
    m12["   sefaz     (service) "]
    m3 ~~~ m5
    m4 ~~~ m6
    m5 ~~~ m7
    m6 ~~~ m8
    m7 ~~~ m9
    m8 ~~~ m10
    m9 ~~~ m11
    m10 ~~~ m12
  end
  aEntry ~~~ aServices
  class m1 tech-nginx
  class m3,m4,m5,m6 tech-nestjs
flowchart LR
  %% kind: system-map
  %% view: expanded
  accTitle: System map — overview (part 1)
  accDescr: OnDelivery's components grouped by area, with the links between them.
  subgraph aEntry["Entry"]
    m1["     nginx                      (proxy · NGINX)"]
    m2["vps‑private‑    bridge      (bridge)  "]
  end
  subgraph aServices["Services"]
    m3["commerce            (service)  NestJS  "]
    m4[" payment            (service)  NestJS  "]
    m5["subscription               (service)      NestJS   "]
    m6["communication                 (service)      NestJS    "]
    m7["evolution‑api   (service)  "]
    m8["affiliates   (service) "]
    m9["   auth      (service) "]
    m10["  catalog    (service) "]
    m11["procurement  (service) "]
    m12["   sefaz     (service) "]
    aServicesAll["  Services   (whole area)"]:::areaAnchor
  end
  subgraph aData["Data"]
    m13[("commerce‑db             (database)  PostgreSQL ")]
    m14[(" auth‑db              (database) PostgreSQL")]
    m15[("scheduling‑db                (database)    PostgreSQL  ")]
    m16[("services‑db             (database)  PostgreSQL ")]
    m17[("evolution‑  service              (database) PostgreSQL")]
    m18[("    db                  (database)  PostgreSQL ")]
    m19[("     cache                      (cache · Redis)")]
    m20[" rabbitmq                 (queue)    RabbitMQ "]
    m21[("Postgres‑PITR   (storage)  ")]
    aDataAll["    Data     (whole area)"]:::areaAnchor
  end
  subgraph aObservability["Observability"]
    m22["    grafana     (observability)     Grafana   "]
    m23["  prometheus    (observability)   Prometheus   "]
    m24["     loki       (observability)      Loki   "]
    m25["     tempo      (observability)      Tempo   "]
    m26["otel‑collector  (observability)  OpenTelemetry     Collector   "]
    m27["   cadvisor     (observability)    cAdvisor   "]
    m28[" node‑exporter  (observability)  Node Exporter   "]
  end
  m1 ---|visible link| aServicesAll
  aServicesAll ---|visible link| aDataAll
  m6 ---|visible link| m7
  classDef areaAnchor fill:none,stroke-dasharray:6 4
  class m1 tech-nginx
  class m3,m4,m5,m6 tech-nestjs
  class m13,m14,m15,m16,m17,m18 tech-postgresql
  class m19 tech-redis
  class m20 tech-rabbitmq
flowchart TB
  %% kind: system-map
  %% "~~~" lines are invisible layout hints, not links
  accTitle: System map — overview (part 2)
  accDescr: OnDelivery's components grouped by area.
  subgraph aData["Data"]
    direction TB
    m13[("commerce‑db             (database)  PostgreSQL ")]
    m14[(" auth‑db              (database) PostgreSQL")]
    m15[("scheduling‑db                (database)    PostgreSQL  ")]
    m16[("services‑db             (database)  PostgreSQL ")]
    m17[("evolution‑  service              (database) PostgreSQL")]
    m18[("    db                  (database)  PostgreSQL ")]
    m19[("     cache                      (cache · Redis)")]
    m20[" rabbitmq                 (queue)    RabbitMQ "]
    m21[("Postgres‑PITR   (storage)  ")]
    m13 ~~~ m15
    m14 ~~~ m16
    m15 ~~~ m17
    m16 ~~~ m18
    m17 ~~~ m19
    m18 ~~~ m20
    m19 ~~~ m21
  end
  class m13,m14,m15,m16,m17,m18 tech-postgresql
  class m19 tech-redis
  class m20 tech-rabbitmq
flowchart TB
  %% kind: system-map
  %% "~~~" lines are invisible layout hints, not links
  accTitle: System map — overview (part 3)
  accDescr: OnDelivery's components grouped by area.
  subgraph aObservability["Observability"]
    direction TB
    m22["    grafana     (observability)     Grafana   "]
    m23["  prometheus    (observability)   Prometheus   "]
    m24["     loki       (observability)      Loki   "]
    m25["     tempo      (observability)      Tempo   "]
    m26["otel‑collector  (observability)  OpenTelemetry     Collector   "]
    m22 ~~~ m23
    m23 ~~~ m24
    m24 ~~~ m25
    m25 ~~~ m26
  end
flowchart TB
  %% kind: system-map
  %% "~~~" lines are invisible layout hints, not links
  accTitle: System map — overview (part 4)
  accDescr: OnDelivery's components grouped by area.
  subgraph aObservability41["Observability (continued)"]
    direction TB
    m27["   cadvisor     (observability)    cAdvisor   "]
    m28[" node‑exporter  (observability)  Node Exporter   "]
    m27 ~~~ m28
  end

Details by area

Entry

Where requests come in and what each component connects to. A dashed box is the other end of a link, with its area in parentheses; (whole area) is a link with the area as a whole.

flowchart LR
  %% kind: system-map-detail entry
  accTitle: System map — Entry
  accDescr: OnDelivery's components grouped by area.
  m1["     nginx                      (proxy · NGINX)"]
  m2["vps‑private‑    bridge      (bridge)  "]
  r1["  Services   (whole area)"]:::ref
  m1 --- r1
  classDef ref fill:none,stroke-dasharray:4 3
  class m1 tech-nginx
flowchart LR
  %% kind: system-map-detail entry
  %% view: expanded
  accTitle: System map — Entry
  accDescr: OnDelivery's components grouped by area, with the links between them.
  m1["     nginx                      (proxy · NGINX)"]
  m2["vps‑private‑    bridge      (bridge)  "]
  r1["  Services   (whole area)"]:::ref
  m1 ---|visible link| r1
  classDef ref fill:none,stroke-dasharray:4 3
  class m1 tech-nginx

Services

The services and their links. A dashed box is the other end of a link, with its area in parentheses; (whole area) is a link with the area as a whole and (this area) marks another service of the same area. A service with many links may appear in more than one part.

flowchart LR
  %% kind: system-map-detail services
  accTitle: System map — Services (part 1)
  accDescr: OnDelivery's components grouped by area.
  m3["commerce            (service)  NestJS  "]
  m4[" payment            (service)  NestJS  "]
  m5["subscription               (service)      NestJS   "]
  class m3,m4,m5 tech-nestjs
flowchart LR
  %% kind: system-map-detail services
  accTitle: System map — Services (part 2)
  accDescr: OnDelivery's components grouped by area.
  m6["communication                 (service)      NestJS    "]
  m7["evolution‑api   (service)  "]
  m8["affiliates   (service) "]
  r1["evolution‑api  (this area)  "]:::ref
  m6 --- r1
  classDef ref fill:none,stroke-dasharray:4 3
  class m6 tech-nestjs
flowchart LR
  %% kind: system-map-detail services
  %% view: expanded
  accTitle: System map — Services (part 2)
  accDescr: OnDelivery's components grouped by area, with the links between them.
  m6["communication                 (service)      NestJS    "]
  m7["evolution‑api   (service)  "]
  m8["affiliates   (service) "]
  r1["evolution‑api  (this area)  "]:::ref
  m6 ---|visible link| r1
  classDef ref fill:none,stroke-dasharray:4 3
  class m6 tech-nestjs
flowchart LR
  %% kind: system-map-detail services
  accTitle: System map — Services (part 3)
  accDescr: OnDelivery's components grouped by area.
  m9["   auth      (service) "]
  m10["  catalog    (service) "]
  m11["procurement  (service) "]
flowchart LR
  %% kind: system-map-detail services
  accTitle: System map — Services (part 4)
  accDescr: OnDelivery's components grouped by area.
  m12["   sefaz     (service) "]
  aSelf["  Services   (whole area)"]:::wholeArea
  r1[" nginx  (Entry)"]:::ref
  r2["    Data     (whole area)"]:::ref
  aSelf --- r1
  aSelf --- r2
  classDef ref fill:none,stroke-dasharray:4 3
  classDef wholeArea font-weight:bold
flowchart LR
  %% kind: system-map-detail services
  %% view: expanded
  accTitle: System map — Services (part 4)
  accDescr: OnDelivery's components grouped by area, with the links between them.
  m12["   sefaz     (service) "]
  aSelf["  Services   (whole area)"]:::wholeArea
  r1[" nginx  (Entry)"]:::ref
  r2["    Data     (whole area)"]:::ref
  aSelf ---|visible link| r1
  aSelf ---|visible link| r2
  classDef ref fill:none,stroke-dasharray:4 3
  classDef wholeArea font-weight:bold

Data

Databases, cache, queue and storage, and what each one connects to. A dashed box is the other end of a link, with its area in parentheses; (whole area) is a link with the area as a whole.

flowchart LR
  %% kind: system-map-detail data
  accTitle: System map — Data (part 1)
  accDescr: OnDelivery's components grouped by area.
  m13[("commerce‑db             (database)  PostgreSQL ")]
  m14[(" auth‑db              (database) PostgreSQL")]
  class m13,m14 tech-postgresql
flowchart LR
  %% kind: system-map-detail data
  accTitle: System map — Data (part 2)
  accDescr: OnDelivery's components grouped by area.
  m15[("scheduling‑db                (database)    PostgreSQL  ")]
  m16[("services‑db             (database)  PostgreSQL ")]
  class m15,m16 tech-postgresql
flowchart LR
  %% kind: system-map-detail data
  accTitle: System map — Data (part 3)
  accDescr: OnDelivery's components grouped by area.
  m17[("evolution‑  service              (database) PostgreSQL")]
  m18[("    db                  (database)  PostgreSQL ")]
  class m17,m18 tech-postgresql
flowchart LR
  %% kind: system-map-detail data
  accTitle: System map — Data (part 4)
  accDescr: OnDelivery's components grouped by area.
  m19[("     cache                      (cache · Redis)")]
  m20[" rabbitmq                 (queue)    RabbitMQ "]
  class m19 tech-redis
  class m20 tech-rabbitmq
flowchart LR
  %% kind: system-map-detail data
  accTitle: System map — Data (part 5)
  accDescr: OnDelivery's components grouped by area.
  m21[("Postgres‑PITR   (storage)  ")]
  aSelf["    Data     (whole area)"]:::wholeArea
  r1["  Services   (whole area)"]:::ref
  aSelf --- r1
  classDef ref fill:none,stroke-dasharray:4 3
  classDef wholeArea font-weight:bold
flowchart LR
  %% kind: system-map-detail data
  %% view: expanded
  accTitle: System map — Data (part 5)
  accDescr: OnDelivery's components grouped by area, with the links between them.
  m21[("Postgres‑PITR   (storage)  ")]
  aSelf["    Data     (whole area)"]:::wholeArea
  r1["  Services   (whole area)"]:::ref
  aSelf ---|visible link| r1
  classDef ref fill:none,stroke-dasharray:4 3
  classDef wholeArea font-weight:bold

Observability

The observability stack: metrics, logs and traces.

flowchart LR
  %% kind: system-map-detail observability
  accTitle: System map — Observability (part 1)
  accDescr: OnDelivery's components grouped by area.
  m22["    grafana     (observability)     Grafana   "]
  m23["  prometheus    (observability)   Prometheus   "]
  m24["     loki       (observability)      Loki   "]
flowchart LR
  %% kind: system-map-detail observability
  accTitle: System map — Observability (part 2)
  accDescr: OnDelivery's components grouped by area.
  m25["     tempo      (observability)      Tempo   "]
  m26["otel‑collector  (observability)  OpenTelemetry     Collector   "]
  m27["   cadvisor     (observability)    cAdvisor   "]
flowchart LR
  %% kind: system-map-detail observability
  accTitle: System map — Observability (part 3)
  accDescr: OnDelivery's components grouped by area.
  m28[" node‑exporter  (observability)  Node Exporter   "]

Delivery and quality

The repository has tests with Jest, Vitest and Playwright, CI on GitHub Actions, Dockerfiles and a Docker Compose file.

Where the CI workflows, the Dockerfiles and the Docker Compose file live in the repository.

flowchart TB
  nRepo["Git repository"]
  subgraph sgAssets["Delivery assets"]
    direction LR
    nCi(["CI workflows"])
    nDocker["Container build files"]
    nCompose["Container orchestration file"]
  end
  nRepo -->|contains| sgAssets

What this architecture costs

  • Monorepo: atomic changes across packages and shared tooling, but the pipeline and accidental coupling between packages need care.
  • Several services: each service deploys and scales on its own, at the cost of more operations, distributed observability and eventual consistency between them.
  • Asynchronous messaging: decouples services in time and absorbs load peaks, but debugging gets harder and consumers must be idempotent.
  • Containers: environment parity and predictable deploys, with the cost of keeping base images up to date.
Original engineering essay
Éverton Carvalho|Software Engineer | Software Architect | CTO

Built with Next.js 15, NestJS 11, PostgreSQL and Prisma. Decoupled, pragmatic architecture.

© 2026 Éverton Carvalho. Todos os direitos reservados.

Portfolio V2 · Zero Shared Runtimes