> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meridian.study/llms.txt
> Use this file to discover all available pages before exploring further.

# Backend Best practices

> Best practices for working in the Meridian backend — multi-tenancy, auth, models, routes, and conventions for developers and AI agents

<Info>
  **Audience**: Developers editing `Meridian/backend/`. Follow these patterns when adding or modifying routes, middlewares, services, and schemas.
</Info>

## Architecture overview

* **Stack**: Express, Mongoose, JWT (cookies + Bearer), optional Passport/SAML.
* **Multi-tenant DB**: Tenant is derived from **subdomain** (e.g. `rpi` from `rpi.meridian.study`). In development, `localhost` and IP hosts default to `rpi`.
* **Per-request DB**: Every request gets `req.db` (a Mongoose connection) and `req.school` (subdomain string) from a global middleware in `app.js`.
* **Global DB**: Every request also gets `req.globalDb` for cross-tenant data (GlobalUser, PlatformRole, TenantMembership, Session). Use only via `getGlobalModelService(req, ...)` in auth and platform-admin logic. See [Multi-Tenant Identity & SSO](/backend/multi-tenant-identity) for full architecture.

<Warning>
  **Never** use a default `mongoose` connection or `mongoose.model()` directly for app data. Always use the request-scoped connection and `getModelService` (see below); use `getGlobalModelService` only for global identity data.
</Warning>

## Database and models

### Getting models (required pattern)

**Always** resolve models through `getModelService` with the current `req`:

```javascript theme={null}
const getModels = require('../services/getModelService');

// In a route or service that has req:
const { User, Event, Org } = getModels(req, 'User', 'Event', 'Org');
```

* **Why**: `req.db` is the tenant-specific Mongoose connection. `getModels(req, ...)` registers schemas on `req.db` and 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 pass `req` and use `getModels(req, ...)` inside the service.
* **Do not**: Use `require('../schemas/user')` and then `mongoose.model('User')` or any global `mongoose` connection for app data. That bypasses multi-tenancy.

### Adding a new model

<Steps>
  <Step title="Add schema">
    Add a schema under `Meridian/backend/schemas/` (or `Meridian/backend/events/schemas/` for event-related models).
  </Step>

  <Step title="Register in getModelService">
    In `Meridian/backend/services/getModelService.js`:

    * Require the schema.
    * Add an entry to the `models` object: `ModelName: req.db.model('ModelName', schema, 'collectionName')`.
    * Use the exact **collection name** (third argument) the app expects (e.g. `'users'`, `'events'`).
  </Step>

  <Step title="Connections">
    No need to touch `connectionsManager.js` for a new model; it only provides `req.db` per school.
  </Step>
</Steps>

### Connections manager

* **File**: `Meridian/backend/connectionsManager.js`
* **Role**: Maintains a pool of Mongoose connections per school and exposes `connectToDatabase(school)`.
* **Usage**: Only `app.js` calls it. The middleware in `app.js` sets `req.db = await connectToDatabase(subdomain)` and `req.school = subdomain`.
* **Adding a school**: Extend the `schoolDbMap` in `getDbUriForSchool()` with the new subdomain and env var (e.g. `MONGO_URI_<SCHOOL>`). Fallback is `DEFAULT_MONGO_URI`.

## Auth and middlewares

### Order of use

Typical order on a route: **auth first**, then **org/permission** (if needed), then handler.

* `verifyToken` or `verifyTokenOptional` must run before any middleware or handler that uses `req.user`.
* Org middlewares (`requireOrgPermission`, etc.) expect `req.user` and use `getModels(req, ...)`; they must run after `verifyToken`.

### verifyToken.js

| Middleware                          | Behavior                                                                                                                                                                                                                                                                      |
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **verifyToken**                     | Reads JWT from `req.cookies.accessToken` or `Authorization: Bearer <token>`. On success sets `req.user = { userId, roles }`. On missing token: `401` with `code: 'NO_TOKEN'`. On expired: `401` with `code: 'TOKEN_EXPIRED'`. On invalid: `403` with `code: 'INVALID_TOKEN'`. |
| **verifyTokenOptional**             | Same token sources. If no token or invalid, continues without `req.user`. If token expired, may try to refresh via `refreshToken` cookie and set new `accessToken` cookie and `req.user`.                                                                                     |
| **authorizeRoles(...allowedRoles)** | Use **after** `verifyToken`. Checks `req.user.roles`; if the user has none of `allowedRoles`, responds `403 Forbidden`.                                                                                                                                                       |

Example:

```javascript theme={null}
const { verifyToken, authorizeRoles } = require('../middlewares/verifyToken');

router.get('/admin-only', verifyToken, authorizeRoles('admin'), async (req, res) => {
  const { User } = getModels(req, 'User');
  // ...
});
```

### orgPermissions.js

Use these for org-scoped actions. They use `getModels(req, 'OrgMember', 'Org')` and expect `req.user` (so use after `verifyToken`).

| Middleware                                               | Purpose                                                                                                                                                                                                 |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **requireOrgPermission(permission, orgParam = 'orgId')** | Resolves org from params/body/query. Ensures user is active member with the given permission; sets `req.orgMember` and `req.org`.                                                                       |
| **requireAnyOrgPermission(permissions, orgParam)**       | Same but user needs at least one of the listed permissions.                                                                                                                                             |
| **requireOrgOwner(orgParam)**                            | Ensures user is the org owner; sets `req.org`.                                                                                                                                                          |
| **Convenience wrappers**                                 | `requireRoleManagement`, `requireMemberManagement`, `requireEventManagement`, `requireAnalyticsAccess`, `requireEquipmentManagement`, `requireEquipmentModification` — all take `(orgParam = 'orgId')`. |

Permission strings must match **constants/permissions.js**. Use the constants in code:

```javascript theme={null}
const { requireOrgPermission } = require('../middlewares/orgPermissions');
const { ORG_PERMISSIONS } = require('../constants/permissions');

router.post('/orgs/:orgId/events', verifyToken, requireOrgPermission(ORG_PERMISSIONS.MANAGE_EVENTS), async (req, res) => {
  // req.org, req.orgMember set
});
```

## Routes

### Structure

* Routes live in `Meridian/backend/routes/` (and under `Meridian/backend/events/routes/` for event features).
* Each file exports an Express `Router`. Mount in `app.js` (or in `events/index.js` for 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:

| Scenario         | Shape                                                                      |
| ---------------- | -------------------------------------------------------------------------- |
| **Success**      | `res.status(200).json({ success: true, data?: any, message?: string })`    |
| **Created**      | `res.status(201).json({ success: true, ... })`                             |
| **Client error** | `res.status(4xx).json({ success: false, message: string, code?: string })` |
| **Server error** | `res.status(500).json({ success: false, message: string })`                |

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 optionally `authorizeRoles` or org permission middlewares) in the route chain. Then you can use `req.user` and `getModels(req, ...)`.
* **Public**: Do not use `verifyToken`. For optional auth (e.g. personalized data when logged in), use `verifyTokenOptional`.

## 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 calls `getModels(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 a `db` property (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.js` is required in `app.js` and mounted as `app.use(eventsRoutes)`.
* **Routes**: Under `Meridian/backend/events/routes/` (e.g. `eventSystemConfigRoutes.js`, `analyticsRoutes.js`). These are aggregated in `events/index.js`.
* **Schemas**: Event-related schemas live in `Meridian/backend/events/schemas/`. They are required and registered in `getModelService.js`; event routes and middlewares use the same `getModels(req, ...)` pattern.

When adding event features: add schemas under `events/schemas/`, register in `getModelService.js`, add routes under `events/routes/`, then mount in `events/index.js` if needed.

## Testing

The backend uses a three-layer testing framework (Jest + Supertest + mongodb-memory-server). See `Meridian/docs/TESTING_FRAMEWORK.md` for full details.

### Layout

| Layer              | Location                                    | Purpose                                                     |
| ------------------ | ------------------------------------------- | ----------------------------------------------------------- |
| **Unit**           | `backend/tests/unit/**/*.test.js`           | Pure utilities (e.g. `semesterHelpers`, `timeBlockHelper`). |
| **Integration**    | `backend/tests/integration/**/*.test.js`    | HTTP route wiring, middleware behavior (e.g. `/health`).    |
| **Route outcomes** | `backend/tests/route-outcomes/**/*.test.js` | End-to-end API correctness against in-memory MongoDB.       |

### Commands

From repo root:

* `npm run test:backend` — backend coverage
* `npm run test:ci` — backend + frontend (CI gate)

From backend:

* `npm --prefix backend run test:unit`
* `npm --prefix backend run test:integration`
* `npm --prefix backend run test:routes` — route-outcome tests
* `npm --prefix backend run test:coverage`

### Multi-tenant route-outcome tests

Route-outcome tests must set `req.db`, `req.school`, and (when applicable) `req.globalDb` so handlers get the correct tenant context:

```javascript theme={null}
app.use((req, _res, next) => {
  req.db = mongo.connection;
  req.globalDb = mongo.globalConnection;  // when route uses authGlobalService
  req.school = 'rpi';
  next();
});
```

For auth routes that call `authGlobalService` (e.g. register, login):

* Use `createMongoMemoryConnection({ withGlobalDb: true })`.
* Set `req.globalDb = mongo.globalConnection`.
* Mock `createGlobalSession` in `sessionUtils` (tests use in-memory DB, not global Session persistence).

When adding new route-outcome tests (e.g. for Events-Backend routes mounted via `backend/events`), mock `backendRoot` for `verifyToken`, `requireAdmin`, and `getModelService`; use `modulePaths: ['<rootDir>/node_modules']` in Jest so Events-Backend files resolve backend deps correctly.

## Permissions constants

* **File**: `Meridian/backend/constants/permissions.js`
* **Exports**: `ORG_PERMISSIONS`, `EVENT_PERMISSIONS`, `USER_PERMISSIONS`, `SYSTEM_PERMISSIONS`, `PERMISSION_GROUPS`, `PERMISSION_DESCRIPTIONS`, plus helpers like `getPermissionDescription`, `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

| Concern               | Rule                                                                                                                                                |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| **DB / models**       | Always `getModels(req, 'ModelName', ...)` with the request-scoped `req`. Never use global `mongoose` for app data.                                  |
| **Tenant**            | Use `req.db` and `req.school` set by app middleware; do not infer tenant elsewhere.                                                                 |
| **Auth**              | Use `verifyToken` for protected routes; `verifyTokenOptional` for optional auth.                                                                    |
| **Roles**             | Use `authorizeRoles(...)` after `verifyToken` for role checks.                                                                                      |
| **Org permissions**   | Use `requireOrgPermission` / `requireAnyOrgPermission` / `requireOrgOwner` from `orgPermissions.js` with constants from `constants/permissions.js`. |
| **Responses**         | Use `{ success, message?, code?, data? }` JSON and appropriate status codes.                                                                        |
| **New model**         | Add schema, then register in `getModelService.js` with `req.db.model('Name', schema, 'collectionName')`.                                            |
| **New school/tenant** | Add mapping in `connectionsManager.js` `getDbUriForSchool` and corresponding env var.                                                               |

## Known inconsistencies and gotchas

<Warning>
  Be aware of these when editing the backend.
</Warning>

* **Duplicate key in getModelService**: The `models` object in `getModelService.js` defines `EventAnalytics` twice (same schema/collection). Prefer a single entry to avoid confusion.
* **Token expiry**: `verifyToken.js` and `authRoutes.js` use different `ACCESS_TOKEN_EXPIRY` values (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.verify` and calls `next()` from inside the callback; ensure `next()` is never called twice (e.g. on refresh path) to avoid double-response issues.
* **StudySession service**: `studySessionService.js` expects `req` and calls `getModels(req, ...)`. Any caller must pass the real `req` from the route.

## Quick reference: key files

| Purpose                                         | File(s)                                                                 |
| ----------------------------------------------- | ----------------------------------------------------------------------- |
| App entry, CORS, DB middleware, route mounting  | `Meridian/backend/app.js`                                               |
| Per-tenant DB connections                       | `Meridian/backend/connectionsManager.js`                                |
| Request-scoped models                           | `Meridian/backend/services/getModelService.js`                          |
| JWT auth                                        | `Meridian/backend/middlewares/verifyToken.js`                           |
| Org permission checks                           | `Meridian/backend/middlewares/orgPermissions.js`                        |
| Permission constants                            | `Meridian/backend/constants/permissions.js`                             |
| Route definitions                               | `Meridian/backend/routes/*.js`, `Meridian/backend/events/routes/*.js`   |
| Event route mounting                            | `Meridian/backend/events/index.js`                                      |
| Mongoose schemas                                | `Meridian/backend/schemas/*.js`, `Meridian/backend/events/schemas/*.js` |
| Test helpers, unit, integration, route-outcomes | `Meridian/backend/tests/`, `Meridian/docs/TESTING_FRAMEWORK.md`         |

## Related pages

<CardGroup cols={2}>
  <Card title="Authentication overview" icon="shield" href="/backend/auth/overview">
    Login methods, JWT access/refresh, web cookies vs mobile headers, and admin MFA.
  </Card>

  <Card title="Multi-tenant identity & SSO" icon="users" href="/backend/multi-tenant-identity">
    Global users, memberships, platform roles, and per-request `req.user` resolution.
  </Card>

  <Card title="Session management" icon="rotate" href="/backend/auth/sessions">
    Global sessions, device metadata, and revoke endpoints used after login.
  </Card>

  <Card title="SAML" icon="key" href="/backend/auth/SAML">
    Institution SSO per school and how it issues the same token model.
  </Card>

  <Card title="Multi-tenant test scenarios" icon="list" href="/backend/multi-tenant-test-scenarios">
    Route-outcome tests and tenant isolation patterns for auth-aware code.
  </Card>

  <Card title="Testing" icon="flask" href="/testing">
    Backend test layout, CI jobs, and conventions aligned with this repo.
  </Card>

  <Card title="Atlas backend" icon="route" href="/atlas/backend">
    Org and event routes; pairs with `getModels` and permission middleware.
  </Card>

  <Card title="Atlas architecture" icon="sitemap" href="/atlas/architecture">
    How Atlas is wired: models, routes, permissions, and UIs.
  </Card>

  <Card title="Atlas permissions" icon="key" href="/atlas/permissions">
    Org roles, permission constants, and `orgPermissions` usage.
  </Card>

  <Card title="Web client best practices" icon="browser" href="/frontend/best-practices">
    `useFetch`, `postRequest`, routing, and styling next to these APIs.
  </Card>
</CardGroup>
