init-db.js gates startup behind Postgres readiness and PostGIS extension creation, then server.js composes Express with sessions-first ordering and hands a shared "s" toolkit to every feature module's synced/init/started hooks.
Sessions are wired before helmet and bodyParser, not after — cssoHandler depends on req.session existing, so reordering middleware here will silently strip user identity.
Process startup
npm start runs two scripts in sequence:
node scripts/init-db.js && node scripts/server.js
scripts/init-db.js is a one-shot bootstrapper. It opens a connection with no database name, runs CREATE DATABASE for DB_NAME (and, when STAC/tipg/titiler-pgstac are enabled, for mmgis-stac), then reconnects to the new database and ensures the and btree_gist extensions, the connect-pg-simple session table, and the GIST indexes on user_features all exist. It treats Postgres error code 42P04 ("database already exists") as expected and any other code as fatal. The script exits 0 on success or 1 on failure, which is what gates the &&.
Once the DB is ready, scripts/server.js runs. The shared Sequelize instance is in API/connection.js (a second pg-promise handle is exposed by API/database.js for the few features that need raw-SQL composition).
Composing Express
server.js builds a single express() app and layers middleware in roughly this order:
app.set("trust proxy", 1)and anexpress-rate-limitcap on/api/.compression()with an image-content-type bypass.helmet()with a permissive CSP (Leaflet/Cesium/Mapbox tile URLs vary widely) andframeAncestors/frameSrcdriven by env.- Pug as the view engine (login, admin login, error, and SPA shell are all Pug).
cssoHandler— runs on every request, normalizesreq.userandreq.groupsfrom either proxy headers orreq.session.user.bodyParser(500 MB cap, sinceDrawandDatasetsaccept large GeoJSON payloads),cookieParser,cors().- Swagger UI mounted at
${ROOT_PATH}/api/docsfromdocs/mmgis-openapi.json.
Sessions are wired up earlier, before any of the above, because cssoHandler reads req.session:
const pool = new Pool({ /* DB_* env */ });
app.use(session({
secret: process.env.SECRET || "Shhhh, it is a secret!",
name: "MMGISSession",
store: new (require("connect-pg-simple")(session))({ pool }),
resave: false, saveUninitialized: false, proxy: true,
cookie: cookieOptions,
}));
The session store is the session table that init-db.js created. How that session becomes a logged-in user — including the long-term-token path — is covered in auth and sessions.
Per-route guards
server.js defines four guard factories that the feature modules pull off the shared s object:
ensureUser()— redirects unauthenticated requests to the login Pug template, or validates a Bearer long-term token ifAuthorizationis present.ensureAdmin(toLoginPage, denyLongTermTokens, allowGets, allowPosts, disallow)— gate for write endpoints; permission111/110sessions pass, plus a hard-coded allowlist of read-only endpoints.ensureGroup(allowedGroups)— CSSO group check; no-ops whenAUTH != "csso".stopGuests— rejects theguestuser except on a small allowlist.
A separate scripts/middleware.js exports middleware.missions(ROOT_PATH), a path-traversal-hardened static guard used in front of express.static for the /Missions tile/asset tree. It also implements the _time_ URL convention used to serve time-windowed tile composites via sharp.
Mounting feature modules
The whole server build is wrapped inside a callback from setups.getBackendSetups, which is the plugin loader. It scans API/Backend/ for sibling directories (skipping names starting with _ or .), then scans API/ for any *Private-Backend* or *Plugin-Backend* directories and requires each of their nested setups. Every loaded module is keyed by directory name and sorted by priority. Three lifecycle hooks are returned: synced (after sequelize.sync()), init (before httpServer.listen), and started (after the server is listening):
setups.getBackendSetups((setups) => {
sequelize.sync().then(() => setups.synced(s)); // models registered
// ...statics, swagger, env validation...
setups.init(s); // routes mounted
httpServer.listen(port, () => {
setups.started(s); // post-listen hooks
if (process.env.ENABLE_MMGIS_WEBSOCKETS) websocket.init(httpServer);
});
});
The s object passed into each hook bundles app, the four guards, swaggerUi, permissions, and ROOT_PATH. That contract is what every feature module under API/Backend/<Name>/setup.js consumes — see feature modules for the shape they implement.
WebSocket upgrade
API/websocket.js does not bind its own port. websocket.init(httpServer) creates a ws.Server({ noServer: true }) and attaches an upgrade handler to the existing HTTP/HTTPS server. Only requests whose pathname matches WEBSOCKET_ROOT_PATH (or ROOT_PATH) followed by / are handed to wss.handleUpgrade; anything else has its socket destroyed. The default behaviour is a simple broadcast bus — every received message is forwarded to all open clients — used by the Draw and presence features.
logger (API/logger.js) is a thin Winston wrapper used everywhere in this path; it pretty-prints in development and emits one JSON line per call in production, with bodies/queries cropped and password redacted. utils.isDocker() is used during boot to set IS_DOCKER for the rendered SPA shell and for the adjacent-servers proxy initializer.
The ORM MMGIS uses for table definitions, model hooks, and the
connect-pg-simple-backed session store. Spatial queries bypass it and use raw SQL
through pg-promise.
The Postgres spatial extension MMGIS depends on. Installed by init-db.js via
CREATE EXTENSION IF NOT EXISTS postgis. Backs every Geodataset, Draw user-feature,
and spatial query.
The mission-control single-sign-on integration. When AUTH=csso is set,
cssoHandler middleware reads upstream proxy headers to populate req.user and
req.groups; ensureGroup then checks group membership. No-op otherwise.
The repeating shape every backend feature follows, and a tour of the modules that matter (Datasets, Geodatasets, Draw, Config, Stac, Webhooks, Shortener, etc.).
Related
- Backend (API server) — Feature modules under API/BackendThe repeating shape every backend feature follows, and a tour of the modules that matter (Datasets, Geodatasets, Draw, Config, Stac, Webhooks, Shortener, etc.).
- Backend (API server) — Auth, accounts, and sessionsHow users sign in, the first-user-becomes-admin flow, long-term tokens for programmatic access, and Postgres-backed Express sessions.