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.
