Audience: Developers and AI agents editing
Meridian/backend/. Follow these patterns when adding or modifying routes, middlewares, services, and schemas.Architecture overview
- Stack: Express, Mongoose, JWT (cookies + Bearer), optional Passport/SAML.
- Multi-tenant DB: Tenant is derived from subdomain (e.g.
rpifromrpi.meridian.study). In development,localhostand IP hosts default torpi. - Per-request DB: Every request gets
req.db(a Mongoose connection) andreq.school(subdomain string) from a global middleware inapp.js.
Database and models
Getting models (required pattern)
Always resolve models throughgetModelService with the current req:
- Why:
req.dbis the tenant-specific Mongoose connection.getModels(req, ...)registers schemas onreq.dband returns the correct model instances for that tenant. - Where: Use in routes and in services that receive
req(e.g.userServices.js,studySessionService.js). If you add a new service that touches the DB, have the route passreqand usegetModels(req, ...)inside the service. - Do not: Use
require('../schemas/user')and thenmongoose.model('User')or any globalmongooseconnection for app data. That bypasses multi-tenancy.
Adding a new model
1
Add schema
Add a schema under
Meridian/backend/schemas/ (or Meridian/backend/events/schemas/ for event-related models).2
Register in getModelService
In
Meridian/backend/services/getModelService.js:- Require the schema.
- Add an entry to the
modelsobject:ModelName: req.db.model('ModelName', schema, 'collectionName'). - Use the exact collection name (third argument) the app expects (e.g.
'users','events').
3
Connections
No need to touch
connectionsManager.js for a new model; it only provides req.db per school.Connections manager
- File:
Meridian/backend/connectionsManager.js - Role: Maintains a pool of Mongoose connections per school and exposes
connectToDatabase(school). - Usage: Only
app.jscalls it. The middleware inapp.jssetsreq.db = await connectToDatabase(subdomain)andreq.school = subdomain. - Adding a school: Extend the
schoolDbMapingetDbUriForSchool()with the new subdomain and env var (e.g.MONGO_URI_<SCHOOL>). Fallback isDEFAULT_MONGO_URI.
Auth and middlewares
Order of use
Typical order on a route: auth first, then org/permission (if needed), then handler.verifyTokenorverifyTokenOptionalmust run before any middleware or handler that usesreq.user.- Org middlewares (
requireOrgPermission, etc.) expectreq.userand usegetModels(req, ...); they must run afterverifyToken.
verifyToken.js
Example:
orgPermissions.js
Use these for org-scoped actions. They usegetModels(req, 'OrgMember', 'Org') and expect req.user (so use after verifyToken).
Permission strings must match constants/permissions.js. Use the constants in code:
Routes
Structure
- Routes live in
Meridian/backend/routes/(and underMeridian/backend/events/routes/for event features). - Each file exports an Express
Router. Mount inapp.js(or inevents/index.jsfor event routes). - Prefer middleware + single handler per path; keep handlers thin and delegate to services when logic is non-trivial.
Response shape
Use a consistent JSON shape so clients and agents can rely on it:
Auth middlewares already use
success, message, and code. Use code for stable client handling (e.g. NO_TOKEN, TOKEN_EXPIRED, INVALID_TOKEN).
Protected vs public
- Protected: Put
verifyToken(and optionallyauthorizeRolesor org permission middlewares) in the route chain. Then you can usereq.userandgetModels(req, ...). - Public: Do not use
verifyToken. For optional auth (e.g. personalized data when logged in), useverifyTokenOptional.
Services
- Location:
Meridian/backend/services/ - Role: Encapsulate business logic, external APIs, and shared helpers. Keep route handlers focused on HTTP and validation.
- DB access: If a service needs DB, the caller (route or other service) must pass
req. The service then callsgetModels(req, 'ModelName', ...)and uses the returned models. Example:userServices.js,studySessionService.js. - No req in context: For background jobs or scripts without
req, obtain a Mongoose connection and pass something that has adbproperty (or refactor to accept a connection/model factory) so tenant and model resolution still work. Do not introduce a global default connection for app data.
Events submodule
- Mount:
Meridian/backend/events/index.jsis required inapp.jsand mounted asapp.use(eventsRoutes). - Routes: Under
Meridian/backend/events/routes/(e.g.eventSystemConfigRoutes.js,analyticsRoutes.js). These are aggregated inevents/index.js. - Schemas: Event-related schemas live in
Meridian/backend/events/schemas/. They are required and registered ingetModelService.js; event routes and middlewares use the samegetModels(req, ...)pattern.
events/schemas/, register in getModelService.js, add routes under events/routes/, then mount in events/index.js if needed.
Permissions constants
- File:
Meridian/backend/constants/permissions.js - Exports:
ORG_PERMISSIONS,EVENT_PERMISSIONS,USER_PERMISSIONS,SYSTEM_PERMISSIONS,PERMISSION_GROUPS,PERMISSION_DESCRIPTIONS, plus helpers likegetPermissionDescription,validatePermission. - Usage: Use these constants instead of string literals when checking or assigning permissions (e.g. in
requireOrgPermission(ORG_PERMISSIONS.MANAGE_EVENTS)). This keeps permission names consistent and refactor-safe.
Conventions summary
Known inconsistencies and gotchas
- Duplicate key in getModelService: The
modelsobject ingetModelService.jsdefinesEventAnalyticstwice (same schema/collection). Prefer a single entry to avoid confusion. - Token expiry:
verifyToken.jsandauthRoutes.jsuse differentACCESS_TOKEN_EXPIRYvalues (e.g. 15m vs 1m). Align these in one place (e.g. a shared auth constants file) so middleware and token issuance stay in sync. - verifyTokenOptional: Uses a callback-style
jwt.verifyand callsnext()from inside the callback; ensurenext()is never called twice (e.g. on refresh path) to avoid double-response issues. - StudySession service:
studySessionService.jsexpectsreqand callsgetModels(req, ...). Any caller must pass the realreqfrom the route.
Quick reference: key files
Related pages
Backend best practices
Canonical patterns for
getModels, verifyToken, routes, and responses.Multi-tenant identity & SSO
When to use
getGlobalModelService and how req.user is resolved from JWTs.Authentication overview
How login, OAuth, and sessions fit together for correct
req usage in generated code.Testing
How to validate route changes with Supertest and tenant fixtures.
Atlas backend
Org and event HTTP surfaces agents often touch.
Atlas architecture
Product wiring: models, routes, permissions, and UIs.
Atlas permissions
Org roles and middleware enforcement.
Session management
Refresh and multi-device behavior when generating client or server flows.