Getting Started
This page takes you from nothing to all four runnable applications on one machine. Every command below was executed against the real repositories before it was written down; where a repository README disagrees with what actually runs, the difference is called out.
Local development status: Shipped. The web server, the mobile client, the desktop client against its mock, and this docs site all start and run today. The worker tier that every architecture document names as the data source is Not built — there is nothing to install for it. See the system map.
What talks to what, locally
Section titled “What talks to what, locally”Two of the three clients are clients of the Laravel server. The third is not.
demostatics-web (Laravel 11) php artisan serve -> 127.0.0.1:8000 | | /api/v1 (Sanctum tokens) | +-------------------------> demostatics-mobile_application | Expo dev server -> :8081 | +-------------------------> browser (Blade pages)
demostatics-pc_application (Rust) | | WebSocket envelopes, contract v0.2 | +-------------------------> examples/mock-stream-server ws://127.0.0.1:9001 (in-repo mock, NOT the Laravel server)
demostatics-docs (Astro + Starlight) pnpm dev -> :4321 no server dependencyThe desktop client never contacts demostatics-web. A search for api/v1, laravel or sanctum across the whole PC repository, excluding target/ and dist/, returns zero files. Details on the desktop page and in the two contracts.
Prerequisites
Section titled “Prerequisites”| Tool | Version | Needed by |
|---|---|---|
| PHP | 8.3+ (demostatics-web/composer.json requires ^8.3) | web |
| PHP extensions | curl, dom, libxml — plus pdo_sqlite for the default database driver | web |
| Composer | 2 | web |
| Node.js | 20.19.4+ or 22.13+ (see the caution below) | web, mobile, docs |
| npm | whatever ships with your Node | web, mobile |
| pnpm | 7.1+ (demostatics-docs has a pnpm-lock.yaml; Astro’s own engines field asks for pnpm >=7.1.0) | docs |
| Rust | stable toolchain. rust-toolchain.toml pins channel = "stable" with rustfmt and clippy; Cargo.toml declares rust-version = "1.75" as the minimum | desktop |
just | Optional. Every recipe in demostatics-pc_application/justfile except just demo is a one-line cargo command, given below; just demo is a short bash script that runs the mock server and the client together | desktop |
| Linux GUI dev packages | libxkbcommon-dev, libwayland-dev, libgl1-mesa-dev, libx11-dev | desktop, gui feature only |
| Expo Go on a device, or an Android/iOS simulator | — | mobile |
composer.json declares only ext-curl, ext-dom and ext-libxml. pdo_sqlite is not declared but is required in practice, because DB_CONNECTION defaults to sqlite and nothing else will open the database file.
The Rust toolchain file pins the channel, not a version number. Verification here ran on rustc 1.97.1.
Start the server first
Section titled “Start the server first”demostatics-web is the only implemented server on the platform and the only identity authority. The mobile client is useless without it — it has no mock and no fixture data, so every screen renders empty or errors until the API answers. Start it before you touch anything else.
git clone https://github.com/Demostatics/demostatics-web.gitcd demostatics-web
composer installcp .env.example .envphp artisan key:generate
php artisan migrate --seed # creates database/database.sqlite, seeds roles, # categories, communities, editors, questions
npm installnpm run build # compiles into public/build — required, see below
php artisan serve # http://127.0.0.1:8000npm run build is not optional. public/build is gitignored, the Blade layouts @vite those assets, and without them every page renders as unstyled HTML.
Check the server is up and, more usefully, what state it is in:
curl -s -H 'Accept: application/json' http://127.0.0.1:8000/api/v1/healthThe real response, from a machine with demo data loaded:
{ "status": "ok", "app": "Demostatics", "api": "v1", "world_store": true, "can_register": false, "queue": "requires_worker"}Read the last three fields rather than skipping to "status": "ok". They report the three dependencies whose absence is otherwise silent.
| Field | Local value | What it means |
|---|---|---|
world_store | false until you load a geo store | The region/country/state/city dropdowns will be empty. App\Support\GeoData degrades to empty collections instead of throwing, so nothing 500s and nothing warns you |
can_register | false with MAIL_MAILER=log | Verification mail cannot leave the machine, so no new account can complete signup unaided |
queue | requires_worker with QUEUE_CONNECTION=database | Queued jobs are accepted and never run unless you also start php artisan queue:listen |
For live asset rebuilds instead of npm run build plus php artisan serve, composer run dev runs the server, a queue listener and Vite together under concurrently.
More on the app itself is on the web component page.
Load data you can browse
Section titled “Load data you can browse”A plain php artisan migrate --seed gives you roles, categories, communities, editors and questions. It does not give you a forum you can read, reports, or working geo filters. For that:
php artisan db:seed --class=DemoSeederDemoSeeder is written entirely with updateOrCreate/firstOrCreate, so re-running it converges rather than duplicating. It creates:
- Seven users — three documented demo accounts plus four ordinary members so poll result bars have more than three voters.
- Five forum posts, each with a real comment thread, and second-level replies on two of them.
- Five polls with votes already cast, including one deliberately backdated past its own voting window so the closed-poll state renders.
- Four About Us articles and nine editorial articles.
- Twelve reports and seven database items, via
ReportSeederandDatabaseItemSeeder. Those two seeders are invoked only fromDemoSeeder—migrate --seednever creates them. - On the sqlite driver only, a demo world store, by calling
demo:worldfor you.
The geo store can also be built on its own:
php artisan demo:world # add --force to overwrite an existing fileThat writes database/world.sqlite and reports what it created: 3 regions, 3 subregions, 4 countries, 5 states, 6 cities — Americas/Europe/Asia down to Los Angeles, Toronto, Munich and Tokyo. The parent-id columns are indexed, matching the real dr5hn dumps. AppServiceProvider attaches the file as the world schema on every new sqlite connection, so no configuration is needed; the file is gitignored.
Demo credentials
Section titled “Demo credentials”Two separate systems, two unrelated account tables. Nothing is shared between them.
| System | Password | Role or tier | Source | |
|---|---|---|---|---|
| Web and mobile | admin@demostatics.test | password | admin | database/seeders/DemoSeeder.php |
| Web and mobile | editor@demostatics.test | password | editor | database/seeders/DemoSeeder.php |
| Web and mobile | member@demostatics.test | password | user | database/seeders/DemoSeeder.php |
| Desktop mock | free@example.com | demo | free | crates/ds-auth/src/provider.rs |
| Desktop mock | individual@example.com | demo | individual | crates/ds-auth/src/provider.rs |
| Desktop mock | pro@example.com | demo | pro | crates/ds-auth/src/provider.rs |
| Desktop mock | enterprise@example.com | demo | enterprise | crates/ds-auth/src/provider.rs |
DemoSeeder calls markEmailAsVerified() on all seven accounts it creates, so the three above clear the API’s verified tier immediately and can post, vote and open Reports and Database.
Run the mobile client
Section titled “Run the mobile client”Status: Shipped, and a real client of /api/v1. Start the Laravel server first.
git clone https://github.com/Demostatics/demostatics-mobile_application.gitcd demostatics-mobile_application
npm installnpm start # then press a (Android), i (iOS) or w (web)On the web target and the iOS simulator the defaults work with no configuration. src/lib/config.ts resolves the origin to http://localhost:8000, or http://10.0.2.2:8000 on Android, which is the emulator’s alias for the host.
A physical phone cannot resolve your laptop’s localhost. It needs your machine’s LAN address, and Laravel has to be listening on all interfaces rather than loopback only:
# terminal 1, in demostatics-webphp artisan serve --host=0.0.0.0 --port=8000
# terminal 2, in demostatics-mobile_applicationip route get 1.1.1.1 | awk '{print $7; exit}' # your LAN IP, e.g. 192.168.1.42echo 'EXPO_PUBLIC_API_URL=http://192.168.1.42:8000' > .envnpm startGive the origin only, with no path — the client appends /api/v1 itself and strips trailing slashes. EXPO_PUBLIC_ is the Expo prefix that makes a variable readable from client code; renaming it will silently do nothing.
The app’s About screen shows which origin it resolved and whether the geo store is reachable. Check there first when a screen renders empty. More detail on the mobile component page.
Run the desktop client
Section titled “Run the desktop client”Status: Shipped as a vertical slice against an in-repo mock. The server it is designed for is Not built.
git clone https://github.com/Demostatics/demostatics-pc_application.gitcd demostatics-pc_application
just build # or: cargo build --workspacejust build and cargo build --workspace are the same command; just is a convenience, not a requirement. The core workspace builds with no GUI or GL libraries present.
Then, in two terminals:
# terminal A — the local fake backend, WebSocket on 127.0.0.1:9001cargo run -p mock-stream-server # or: just serve
# terminal B — the GUI clientcargo run -p demostatics --features guiFor a windowless run that prints locally computed statistics and exits — useful on a machine with no display, and the fastest way to confirm the pipeline works:
cargo run -p demostatics -- --frames 10 # default build: headless alreadycargo run -p demostatics --features gui -- --headless --frames 10 # gui build: force headlessThe --headless flag is itself #[cfg(feature = "gui")]-gated. Passing it to a build without --features gui fails with error: unexpected argument '--headless' found, because the default build has no other mode to switch away from.
Either invocation logs in as pro@example.com, ingests 10 frames across 7 indicators with 0 gaps, and prints a table of latest, mean, standard deviation and per-frame trend for each indicator, plus the account’s tier, features and usage meters.
On a fresh Linux box the gui feature needs the windowing and GL development packages:
sudo apt-get install -y libxkbcommon-dev libwayland-dev libgl1-mesa-dev libx11-devWindows and macOS need no extra system packages. The GUI is behind the gui cargo feature specifically so the default workspace build and CI need none of this. just demo runs the whole loop end to end — build, start the mock, run 50 frames, stop the mock.
More on the crate layout is on the desktop component page.
Run the docs site
Section titled “Run the docs site”Status: Shipped. This site. It has no server dependency.
git clone https://github.com/Demostatics/demostatics-docs.gitcd demostatics-docs
pnpm install # required first — node_modules is not committedpnpm dev # http://localhost:4321| Command | What it does |
|---|---|
pnpm dev | Dev server on localhost:4321 |
pnpm build | Production build into ./dist, and the only thing that builds the search index |
pnpm preview | Serve ./dist locally |
pnpm astro sync | Regenerate content types after editing src/content.config.ts |
Adding a page means creating the .md file under src/content/docs/ and adding a matching entry to the sidebar array in astro.config.mjs. A sidebar slug with no matching file is a build error. Authoring conventions are on the docs site page.
Making an account you can actually use
Section titled “Making an account you can actually use”Registration requires a verified email, and the API’s verified tier gates every write plus the Reports and Database endpoints. In local development MAIL_MAILER=log, so the verification mail is appended to a log file that nothing reads and nobody is notified about. A freshly registered account can read the forum and nothing else.
GET /api/v1/health reporting can_register: false is how you detect this from outside the machine. Locally, take one of these two routes.
Read the link out of the log
Section titled “Read the link out of the log”Register through the web UI or POST /api/v1/auth/register, then open demostatics-web/storage/logs/laravel.log and follow the verification URL in the most recent entry.
grep -o 'http[^ ]*verify-email[^ ]*' storage/logs/laravel.log | tail -1The URL is signed and expiring, so take the last one, not the first.
Verify by hand
Section titled “Verify by hand”php artisan tinker$u = App\Models\User::where('email', 'you@example.com')->first();$u->markEmailAsVerified();Give yourself a role
Section titled “Give yourself a role”There is no admin UI for role assignment. Roles are granted directly:
php artisan tinkerApp\Models\User::where('email', 'you@example.com')->first()->assignRole('admin');Valid roles are admin, moderator, editor, technical_staff and user. New registrations get user automatically. The enum lives in app/Models/UserRole.php.
If you only want to look around, skip all of this and sign in as admin@demostatics.test — DemoSeeder already verified it and assigned the role.
Tests and checks
Section titled “Tests and checks”| Repo | Command | What it covers |
|---|---|---|
demostatics-web | php artisan test | 211 tests, 922 assertions (Pest). Web pages, JSON API v1, auth and email verification, moderation, presence, report exports, and portability against a missing world store. Runs on in-memory sqlite with MAIL_MAILER=array and the world store deliberately absent — no setup, ~2s |
demostatics-mobile_application | npm test | 102 tests in 6 Jest suites. The HTTP client’s mapping of responses onto typed errors, the zustand session store’s token handling, formatters, and the shared error/empty UI states |
demostatics-mobile_application | npm run typecheck | tsc --noEmit across the app |
demostatics-mobile_application | npm run lint | ESLint, including the React Compiler rules |
demostatics-mobile_application | npm run build:web | Static Expo export — catches bundler-level breakage that type checking misses |
demostatics-pc_application | cargo test --workspace | 99 tests across the workspace, including an end-to-end WebSocket roundtrip in ds-stream. Default features only, so the gui, remote and gRPC-gated tests do not run |
demostatics-pc_application | cargo clippy --workspace --all-targets -- -D warnings | Lints at CI strictness |
demostatics-pc_application | cargo fmt --all --check | Formatting |
demostatics-docs | pnpm build | Full Astro build. This is the only check the site has |
demostatics-backend | none | The repository contains one README of two lines and no code. Nothing to run |
The counts above were observed by running each suite, not read from a README. demostatics-mobile_application/README.md states the API suite is 203 tests; it is 211 today.
Common problems
Section titled “Common problems”Every web page renders as unstyled HTML. npm run build was never run, or was run before npm install finished. public/build is gitignored and the Blade layouts @vite those assets, so a fresh clone has no CSS or JS until you build. Run npm install && npm run build, or use composer run dev for a watching Vite.
The geo dropdowns on Reports and Database are empty. No world store is loaded. App\Support\GeoData catches the query exception and returns an empty collection by design, so you get an empty <select> rather than an error. Confirm with curl -s http://127.0.0.1:8000/api/v1/health — world_store: false is the tell. Fix with php artisan demo:world, or by loading a real dr5hn dump.
Reports and Database are empty even with the geo store loaded. ReportSeeder and DatabaseItemSeeder run only from DemoSeeder. php artisan migrate --seed does not invoke them. Run php artisan db:seed --class=DemoSeeder. In production nothing writes those tables at all, because the worker tier is Not built.
The phone shows a network error on every screen. It cannot reach your laptop’s localhost. Set EXPO_PUBLIC_API_URL to the machine’s LAN IP in .env, restart the Expo dev server so the variable is picked up, and serve Laravel with --host=0.0.0.0. Then check the app’s About screen for the origin it actually resolved. A firewall on port 8000 is the next thing to rule out.
The desktop client exits with unknown account. You used a @demostatics.com address from the README. The dev provider only knows free@, individual@, pro@ and enterprise@ example.com, password demo. Passing no --email or --password at all uses the correct defaults.
The desktop client connects but shows no data. mock-stream-server is not running. There is no other server to fall back to — the Laravel instance you started is not a candidate.
pnpm astro check fails on the docs site. @astrojs/check is not a dependency. The command prompts to install it and does nothing useful in a non-interactive shell. Use pnpm build as the check, or add the dependency with pnpm add -D @astrojs/check typescript if you want type-checked frontmatter.
pnpm build fails with bad indentation of a mapping entry. A page’s frontmatter is not valid YAML — almost always an unquoted colon followed by a space inside a title or description. The error names the file and the line. Either remove the colon or quote the whole value.