Skip to content

Folder Structure — Architecture Rationale

Project Structure tells you where things are. This page explains why, the layering (such as it is), and the conventions to follow so new code fits in.

1. The de-facto layering

Prism does not use a classic Controller → Service → Repository architecture. Its real layering is:

flowchart TD
    R[routes/*.js<br/>URL → controller.fn + middleware] --> C[controllers/*.js<br/>request handling + a LOT of business logic]
    C --> CF[commonFunctions/*.js<br/>reusable helpers, queue producers, senders]
    C --> PL[processLibrary/*.js<br/>batch/aggregation engines]
    CRON[crons/*.js<br/>schedulers] --> CF
    CRON --> PL
    CF --> M[(MongoDB via mongoose/native)]
    CF --> S[(MySQL via raw SQL)]
    PL --> M
    PL --> S
    C --> M
    C --> S
Layer Responsibility Reality check
routes/ Map URL+method → controller fn, attach auth Clean and thin ✅
controllers/ Handle req/res ⚠️ Also contain heavy business logic (some files 50+ functions)
commonFunctions/ Shared, reusable operations The closest thing to a "service" layer
processLibrary/ Long-running batch/aggregation "The batch brains"
crons/ Time-based entry points Thin wrappers that call the above
models/ Mongoose schemas only MySQL has no models — raw SQL inline

The key architectural fact: there is no dedicated service/repository layer, and MySQL access is raw SQL scattered across controllers, commonFunctions, and crons. This is the dominant source of duplication and the hardest thing for newcomers. Treat commonFunctions/ as the place to extract shared logic into.

2. Naming conventions observed

Pattern Meaning Examples
*Controller.js route handlers for a feature campaignController.js, loyaltyController.js
*OrderIngestion.js per-source ingestion petpoojaOrderIngestion.js, ristaOrderIngestion.js
*Queue.js (commonFunctions) queue producer/consumer helper smsCampaignQueue.js, pushCampaignQueue.js
send*.js (commonFunctions) channel senders sendSms.js, sendWhatsapp*, sendFcmNotification.js
*Cron.js / crons/* scheduled jobs abandonCartCron.js, rfmSegments.js
*MIS* / *mis* reporting/aggregation campaignMis.js, dashboardMIS.js, models/*_mis.js
handle*Flow.js multi-step domain flows handleWalletPointsFlow.js, handleMilestoneUpgrade.js
process*.js batch processors processCommonIngestion.js, processAutomatedCampaigns.js
*New / v2 / Dynamic suffix ⚠️ newer parallel implementation coexisting with the old dashboardControllerNew.js, crmv2.js, campaignControllerDynamic.js, automatedCampaignQueueNew.js

The *New / Dynamic / v2 suffixes are a migration tell: two implementations of the same thing run side by side. Always confirm which one is wired in routes/ / index.js before editing. See Module Architecture.

3. Subfolders inside crons/

Subfolder Purpose
autoCampaignRoiCrons/ ROI attribution for automated campaigns
rewardsCrons/ loyalty/reward periodic jobs
updationCrons/ data-update/maintenance jobs

4. Where to put new code (guidelines)

You're adding… Put it in… And…
A new endpoint controllers/<feature>.js + register in routes/ keep the controller thin; push logic to commonFunctions/
Reusable logic commonFunctions/ so multiple controllers/crons share it
A scheduled job crons/<name>Cron.js + require in index.js guard against overlap (status doc); use jobsPool for MySQL
A batch/aggregation processLibrary/ write results to a MIS collection
A new Mongo collection models/<name>.js mongoose schema + indexes
MySQL access inline (no ORM) — but centralize the query in commonFunctions/ if reused always parameterize (avoid SQL injection)

5. Anti-patterns to avoid (learned from the codebase)

  • ❌ Copy-pasting a controller and suffixing New/v2 — extend or refactor instead.
  • ❌ Writing raw SQL inline that already exists elsewhere — search commonFunctions/ first.
  • ❌ Adding a cron without overlap protection or without jobsPool.
  • ❌ Cross-store writes (Mongo + MySQL) without idempotency — see the loyalty points cautionary tale in Loyalty.
  • ❌ Hardcoding secrets/hosts — use config.js / env.