Skip to content

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.

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 dependency

The 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.

ToolVersionNeeded by
PHP8.3+ (demostatics-web/composer.json requires ^8.3)web
PHP extensionscurl, dom, libxml — plus pdo_sqlite for the default database driverweb
Composer2web
Node.js20.19.4+ or 22.13+ (see the caution below)web, mobile, docs
npmwhatever ships with your Nodeweb, mobile
pnpm7.1+ (demostatics-docs has a pnpm-lock.yaml; Astro’s own engines field asks for pnpm >=7.1.0)docs
Ruststable toolchain. rust-toolchain.toml pins channel = "stable" with rustfmt and clippy; Cargo.toml declares rust-version = "1.75" as the minimumdesktop
justOptional. 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 togetherdesktop
Linux GUI dev packageslibxkbcommon-dev, libwayland-dev, libgl1-mesa-dev, libx11-devdesktop, gui feature only
Expo Go on a device, or an Android/iOS simulatormobile

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.

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.

Terminal window
git clone https://github.com/Demostatics/demostatics-web.git
cd demostatics-web
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate --seed # creates database/database.sqlite, seeds roles,
# categories, communities, editors, questions
npm install
npm run build # compiles into public/build — required, see below
php artisan serve # http://127.0.0.1:8000

npm 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:

Terminal window
curl -s -H 'Accept: application/json' http://127.0.0.1:8000/api/v1/health

The 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.

FieldLocal valueWhat it means
world_storefalse until you load a geo storeThe 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_registerfalse with MAIL_MAILER=logVerification mail cannot leave the machine, so no new account can complete signup unaided
queuerequires_worker with QUEUE_CONNECTION=databaseQueued 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.

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:

Terminal window
php artisan db:seed --class=DemoSeeder

DemoSeeder 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 ReportSeeder and DatabaseItemSeeder. Those two seeders are invoked only from DemoSeedermigrate --seed never creates them.
  • On the sqlite driver only, a demo world store, by calling demo:world for you.

The geo store can also be built on its own:

Terminal window
php artisan demo:world # add --force to overwrite an existing file

That 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.

Two separate systems, two unrelated account tables. Nothing is shared between them.

SystemEmailPasswordRole or tierSource
Web and mobileadmin@demostatics.testpasswordadmindatabase/seeders/DemoSeeder.php
Web and mobileeditor@demostatics.testpasswordeditordatabase/seeders/DemoSeeder.php
Web and mobilemember@demostatics.testpassworduserdatabase/seeders/DemoSeeder.php
Desktop mockfree@example.comdemofreecrates/ds-auth/src/provider.rs
Desktop mockindividual@example.comdemoindividualcrates/ds-auth/src/provider.rs
Desktop mockpro@example.comdemoprocrates/ds-auth/src/provider.rs
Desktop mockenterprise@example.comdemoenterprisecrates/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.

Status: Shipped, and a real client of /api/v1. Start the Laravel server first.

Terminal window
git clone https://github.com/Demostatics/demostatics-mobile_application.git
cd demostatics-mobile_application
npm install
npm 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 window
# terminal 1, in demostatics-web
php artisan serve --host=0.0.0.0 --port=8000
# terminal 2, in demostatics-mobile_application
ip route get 1.1.1.1 | awk '{print $7; exit}' # your LAN IP, e.g. 192.168.1.42
echo 'EXPO_PUBLIC_API_URL=http://192.168.1.42:8000' > .env
npm start

Give 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.

Status: Shipped as a vertical slice against an in-repo mock. The server it is designed for is Not built.

Terminal window
git clone https://github.com/Demostatics/demostatics-pc_application.git
cd demostatics-pc_application
just build # or: cargo build --workspace

just 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 window
# terminal A — the local fake backend, WebSocket on 127.0.0.1:9001
cargo run -p mock-stream-server # or: just serve
# terminal B — the GUI client
cargo run -p demostatics --features gui

For 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:

Terminal window
cargo run -p demostatics -- --frames 10 # default build: headless already
cargo run -p demostatics --features gui -- --headless --frames 10 # gui build: force headless

The --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:

Terminal window
sudo apt-get install -y libxkbcommon-dev libwayland-dev libgl1-mesa-dev libx11-dev

Windows 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.

Status: Shipped. This site. It has no server dependency.

Terminal window
git clone https://github.com/Demostatics/demostatics-docs.git
cd demostatics-docs
pnpm install # required first — node_modules is not committed
pnpm dev # http://localhost:4321
CommandWhat it does
pnpm devDev server on localhost:4321
pnpm buildProduction build into ./dist, and the only thing that builds the search index
pnpm previewServe ./dist locally
pnpm astro syncRegenerate 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.

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.

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.

Terminal window
grep -o 'http[^ ]*verify-email[^ ]*' storage/logs/laravel.log | tail -1

The URL is signed and expiring, so take the last one, not the first.

Terminal window
php artisan tinker
$u = App\Models\User::where('email', 'you@example.com')->first();
$u->markEmailAsVerified();

There is no admin UI for role assignment. Roles are granted directly:

Terminal window
php artisan tinker
App\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.testDemoSeeder already verified it and assigned the role.

RepoCommandWhat it covers
demostatics-webphp artisan test211 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_applicationnpm test102 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_applicationnpm run typechecktsc --noEmit across the app
demostatics-mobile_applicationnpm run lintESLint, including the React Compiler rules
demostatics-mobile_applicationnpm run build:webStatic Expo export — catches bundler-level breakage that type checking misses
demostatics-pc_applicationcargo test --workspace99 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_applicationcargo clippy --workspace --all-targets -- -D warningsLints at CI strictness
demostatics-pc_applicationcargo fmt --all --checkFormatting
demostatics-docspnpm buildFull Astro build. This is the only check the site has
demostatics-backendnoneThe 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.

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/healthworld_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.