This repository contains the backend API for CyberSim, a tabletop simulation platform designed to help organizations practice responding to complex digital crises.
The backend manages game state, scenario data, database persistence, and Airtable imports.
For a high-level overview of CyberSim, see the CyberSim UI repository: https://github.com/cdoten/CyberSim-UI
- Runtime: Node.js
- Framework: Express
- Database: PostgreSQL
- Query Builder: Knex
- Containerized via Docker
- Designed for AWS Elastic Beanstalk deployment
- Node.js (v22 recommended)
- Docker + Docker Compose (for local Postgres and container builds)
- PostgreSQL (production)
docker compose -f docker-compose-dev.yaml up -d cp .env.example .env npm install npm run reset-db npm start
git clone <REPO_LINK>
cd CyberSim-Backendnpm installcp .env.example .envAdjust values in .env as needed.
This project uses a custom Docker Compose file for development:
docker compose -f docker-compose-dev.yaml up -dTo stop containers:
docker compose -f docker-compose-dev.yaml downReset database (rollback → migrate → seed fixture data):
npm run reset-dbnpm startAPI runs at:
http://localhost:3001
The port is controlled by the PORT environment variable.
Local development typically uses 3001, while production uses 8080.
/src— Application source code/migrations— Database schema migrations/seeds— Fixture data and dataset snapshots/docker-compose-dev.yaml— Local Postgres container for development/Dockerfile— Production container definition
GET /health— Application statusGET /health/db— Database connectivity checkGET /health/airtable— Airtable configuration check
These endpoints are useful during deployment and infrastructure validation.
The project supports two types of seed data.
npm run reset-dbThis will:
- Roll back migrations
- Apply latest migrations
- Load deterministic fixture data
Used for local development and automated tests. This dataset does not depend on Airtable.
Scenario datasets exported from Airtable can be stored under:
seeds/datasets/<scenario>/<revision>/
Example:
seeds/datasets/cso/2026-03-03.1/
Load a dataset snapshot:
SCENARIO_TAG=cso@2026-03-03.1 npm run reset-db:scenarioThis will:
- Reset the database
- Apply migrations
- Load the selected dataset snapshot
After importing from Airtable, export a versioned snapshot of the current database content into the repository so it can be reloaded later without Airtable access:
SCENARIO_TAG=cso@2026-03-03.1 npm run save:scenarioThe tag format is <scenario>@<revision>. This writes a scenario revision to:
seeds/datasets/<scenario>/<revision>/
Apply latest migrations:
npm run migrateRoll back the most recent migration:
npm run rollbackRun seed scripts:
npm run seedRun automated tests:
npm run testTests use the cybersim_test database. Ensure your .env is configured to point to a test database before running tests.
The test suite resets and reseeds the database as part of execution.
PORTNODE_ENVDB_URLUI_ORIGINSAIRTABLE_ACCESS_TOKENAIRTABLE_BASE_IDS
postgres://<USER>:<PASSWORD>@<HOST>:<PORT>/<DB_NAME>
productiondevelopmenttest
Comma-separated list of frontend origins allowed to access the API via CORS. Do not include trailing slashes.
Local development:
UI_ORIGINS=http://localhost:3000
Production (one origin per scenario subdomain):
UI_ORIGINS=https://cso.cybersim.app,https://tnr.cybersim.app
A single Airtable Personal Access Token (PAT) with data.records:read and schema.bases:read scopes. One token can cover multiple bases.
Comma-separated list of slug:baseId pairs mapping each scenario slug to its Airtable base.
AIRTABLE_BASE_IDS=cso:appXXXXXX,tnr:appYYYYYY
The slug must match the subdomain used to access the UI (e.g. cso for cso.cybersim.app).
IMPORT_PASSWORD— Password required to trigger a scenario import via the UI. Import is disabled if not set.SCENARIO_SLUG— Backend fallback slug used when the frontend does not send one (see Multi-Scenario below).LOG_LEVEL— Defaults toerrorin production,infoin development.
fatalerrorwarninfodebugtrace
CyberSim supports multiple independent scenarios served from one backend and one Amplify frontend deployment. The scenario in use is determined by the subdomain.
Frontend (UI): On every page load, the UI reads window.location.hostname and uses the first subdomain segment as the scenario slug:
cso.cybersim.app → slug = "cso"
tnr.cybersim.app → slug = "tnr"
localhost → slug = REACT_APP_SCENARIO_SLUG env var, or "cso" if not set
The slug is sent to the backend as part of the CREATEGAME socket event and the POST /scenario/import request body.
Backend: Uses the slug it receives from the frontend to look up the correct Airtable base in AIRTABLE_BASE_IDS and to scope all game data to the correct scenario in the database.
SCENARIO_SLUG is a backend-only fallback used when the frontend does not send a slug — for example, during manual API calls, scripts, or in a single-scenario deployment where you want to hardcode the scenario server-side. The resolution order is:
slug from frontend request → SCENARIO_SLUG env var → "cso"
See docs/airtable-handbook.md for the full step-by-step process.
See docs/aws-elasticbeanstalk-deployment.md.
See docs/airtable-handbook.md.
The CyberSim facilitation app was originally developed by Rising Stack for the National Democratic Institute (NDI), with support from Microsoft and the National Endowment for Democracy, as part of broader efforts to strengthen civic resilience in the digital age.