Skip to content

API Backend — apps/api ​

An Express + TypeScript service. The only writer to the primary database. Controllers are thin; services hold all business logic (106 service files).

Folder structure ​

text
apps/api/src/
├── index.ts          Entry point — imports instrumentation first, warms pool, starts workers, listens
├── app.ts            Express setup — CORS, helmet, Swagger, Bull Board, health, all route mounts, errorHandler
├── instrumentation.ts Sentry + OpenTelemetry init (must load before anything else)
├── routes/           30 route files — thin: wire middleware → controller/service
├── controllers/      Request/response only (auth, farm, farmer, trader, warehouse, public)
├── services/         All business logic (106 files, *.service.ts)
├── validators/       Zod schemas for every request body/query
├── middleware/       authenticate, authorize, requireActiveUser, quota, rateLimit, upload, errorHandler
├── workers/          BullMQ consumers: smsWorker, scheduledWorker, searchSyncWorker
├── queues/           Queue definitions + enqueuers: smsQueue, scheduledQueue, searchSyncQueue
├── lib/              Cross-cutting helpers (prisma, r2, redis, msg91, serializers, pdf, gst, giEligibility…)
├── utils/            jwt, password, pagination, membership
├── data/             Static reference data
├── types/            Local TypeScript types
└── scripts/          One-off + maintenance scripts (backfills, reindex, QA seed)

Entry point (index.ts → app.ts) ​

index.ts imports ./instrumentation first (OpenTelemetry must patch modules before they load), then boots the Express app from app.ts and starts the workers.

app.ts builds the middleware stack in order:

  1. trust proxy (Railway sits behind a reverse proxy).
  2. CORS with a custom origin check — exact FRONTEND_URL/ADMIN_URL, ecropto.com
    • subdomains, and (non-prod only) any http://localhost:<port>; * is forbidden.
  3. helmet() then express.json().
  4. Swagger UI at /api/docs, spec JSON at /api/docs.json.
  5. Health check at /api/v1/health — deep-probes Postgres + Redis, echoes non-secret config, returns 503 when degraded.
  6. Bull Board at /admin/queues (admin-authenticated).
  7. Gated test endpoints at /api/v1/test (only when ENABLE_TEST_ENDPOINTS=true and a secret is set).
  8. All /api/v1/* route mounts. Admin routes are wrapped with authenticate + authorize('admin').
  9. Sentry.setupExpressErrorHandler(app) then the app's errorHandler — last.

Route → controller → service pattern ​

Most V2 routes call services directly (thin route files); the older farmer/trader/farm/warehouse/public/auth surfaces use dedicated controllers in controllers/.

Middleware ​

MiddlewareResponsibility
authenticateValidate the Authorization: Bearer JWT → attach req.user; set Sentry user (id only). Rejects DEACTIVATED users.
authorize(role)Role gate — admin, farmer, trader.
requireActiveUserprofileComplete guard on all write endpoints.
requireSellQuota / requireBuyQuotaCheck + consume the post-axis quota.
requireProductsV2Feature-flag gate for the products V2 surface.
rateLimit / rateLimitStoreRedis-backed limiters (OTP, login phone/device/IP, search). Fall back to in-memory without Redis.
uploadmulter (memory) → Sharp compress → R2. Validates magic bytes, rejects SVG.
errorHandlerCentral formatter: { success:false, error:{ code, message } } + status; captures 5xx to Sentry.

The lib/ toolbox (selected) ​

FilePurpose
prisma.tsPrisma client singleton + pooled URL builder + warm-up
redis.tsUpstash Redis client (nullable when unconfigured)
r2.tsCloudflare R2 S3 client
msg91.tsMSG91 HTTP client — only called by smsWorker
leadSerializer.tsLead response enrichment + identity masking
dealRowSerializer.ts / engagementRowSerializer.tsDeal / engagement row shaping
serializers/Batch + product serializers (owner-only field stripping)
batchPricing.tsWeighted-average displayPricePerKg, blended cost
giEligibility.tsGI tag eligibility (Mithilanchal districts)
gst.tsNearest-ancestor GST bracket resolution
leadLifecycle.tsLead state-machine rules
unreadMessages.tsBatched unread-count (groupBy, no N+1)
timezone.tsIST date helpers (quota boundaries, buckets)
pdf/ + reports/Deal-document + report PDF generation
messaging.realtime.tsIn-process pub/sub for messaging (EventEmitter in Phase 1)
featureFlags.tsRuntime feature toggles

Workers & queues ​

Three BullMQ queues, consumed by workers running in the API process:

QueueWorkerJobs
sms-notificationssmsWorker.tsSend SMS via MSG91 (3 retries, backoff)
scheduledscheduledWorker.ts8 recurring cron jobs (see Jobs & Workers)
search-syncsearchSyncWorker.tsSync batch documents to Meilisearch

See API Reference for the endpoint catalogue and Data Model for the schema.

Internal technical documentation — Cropto