Skip to content

scheduler-api

apps/scheduler-api is the central API server powering all booking, business management, billing, and orchestration. It exposes two surfaces: a public REST API (contract-first, slug-scoped) and an admin tRPC surface consumed by the portals.

Attribute Value
Framework Fastify (Node.js)
Port 4000
tRPC router src/trpc/router.tsAppRouter
ORM Drizzle on PostgreSQL (Neon in prod)
Queue BullMQ on Redis
Workflows Temporal 1.29.3 (connects to temporal-server.railway.internal:7233)
Real-time SSE for convergence + notifications; WebSocket for chat
OpenAPI Auto-generated at /api/openapi.json, committed to apps/docs/public/

Customer-facing. Contract-first via @nexus/scheduler-contracts — the Zod schemas are the single source of truth. A schema drift is a runtime 500, not a silent type mismatch.

@nexus/scheduler-contracts (Zod)
↓ validate + serialize
scheduler-api (drift = 500 guard)
↓ z.infer
@nexus/scheduler-sdk
@nexus/business-client (hooks)
site-main / site-shop / site-hypno

Portal-facing (both portal and portal-business). End-to-end type-safe via tRPC — types are inferred from the server router, never hand-written on the client.

The 55+ domain modules live in src/modules/:

Module What it does
bookings Booking lifecycle: pending → confirmed → completed / cancelled / no_show
availability Availability rules, blocked times, slot generation
services Service definitions (name, duration, price, staff)
staff Staff records, assignments, schedules
sessions Session management (grouped booking blocks)
customers Customer records, contact info, history
packages Service package bundles
memberships Membership plans
Module What it does
businesses Multi-tenant org management, provisioning triggers
auth JWT/WorkOS token validation, role gating
domains Custom domain registration (Porkbun), ACME, preview URLs
Module What it does
billing Billing scanner (cron), billing gate, invoice mirror, audit log
payments Stripe Connect webhooks, payment state tracking
Module What it does
notifications Unified notification pipeline (email/SMS/in-app via BullMQ)
chat Support ticket threads, SSE streams, WebSocket bridge
Module What it does
convergence Temporal workflows: sync scheduler ↔ Vendure ↔ Directus ↔ WorkOS ↔ Stripe
analytics User behavior tracking
Module What it does
gcal Google Calendar sync
msft-cal Microsoft 365 calendar sync
graph LR
    subgraph "Inbound"
        PB_IN[portal-business\nFastify BFF]
        P_IN[portal\ntRPC client]
        Sites[site-* / scheduler-sdk]
        STR_WH[Stripe webhooks]
        PBK_WH[Porkbun webhooks]
        DIR_WH[Directus webhooks\n→ convergence trigger]
        TMP_CB[Temporal activity callbacks]
    end

    SA[scheduler-api]

    subgraph "Outbound"
        DB[(Neon PostgreSQL\nDrizzle)]
        RD[(Redis\nBullMQ jobs)]
        TMP[Temporal\nworkflow starts]
        VS_OUT[vendure-server\nGraphQL mutations]
        DIR_OUT[directus REST API]
        WOS_OUT[WorkOS API]
        STR_OUT[Stripe API]
        PBK_OUT[Porkbun API]
    end

    PB_IN --> SA
    P_IN --> SA
    Sites --> SA
    STR_WH --> SA
    PBK_WH --> SA
    DIR_WH --> SA
    TMP_CB --> SA

    SA --> DB
    SA --> RD
    SA --> TMP
    SA --> VS_OUT
    SA --> DIR_OUT
    SA --> WOS_OUT
    SA --> STR_OUT
    SA --> PBK_OUT
  • ORM: Drizzle ORM
  • Schema: src/db/schema.ts (single consolidated file, ~108 KB)
  • Migrations: drizzle/ directory; journal-driven (CI checks journal sync)
  • Key tables: businesses, bookings, staff_members, customers, services, availability_overrides, notification_log, notifications, chat_conversations, billing_invoice_mirror, business_billing_audit, platform_config (domain fields are columns on businesses, not a separate table)
File Purpose
src/index.ts Fastify bootstrap + Stripe sync on startup
src/app.ts Fastify app with plugins registered
src/trpc/router.ts tRPC root router (all admin.* procedures)
src/db/schema.ts Drizzle ORM schema (source of truth)
src/modules/billing/stripe-catalog-sync.ts Syncs Stripe products/prices from code on boot