Modash is a local dashboard and proxy for LLM-compatible API traffic. It sits between your application and an upstream API server, forwards requests, records usage metadata in Postgres, and exposes a live React dashboard for usage, throughput, model breakdowns, and recent requests.
The current server tracks:
- Request count, in-flight requests, latency, and per-request throughput.
- Prompt, completion, and total token counts when the upstream response includes usage data.
- Streaming and non-streaming request metadata.
- Per-model token volume and tokens-per-second throughput.
- Live dashboard refresh events over Server-Sent Events.
apps/server: Node HTTP server, proxy, Effect services, RPC handlers, and SSE event stream.apps/web: React/Vite dashboard UI.packages/db: Drizzle schema, migrations, Postgres client, and usage repository.packages/rpc: Shared Effect RPC procedures, schemas, DTOs, and typed errors.
The server exposes three kinds of routes:
/rpc: Effect RPC endpoint used by the dashboard to fetch analytics./events: Server-Sent Events endpoint used by the dashboard to refresh when usage or request state changes.- All other paths: proxied to
UPSTREAM_URL.
The Vite dev server runs the dashboard on port 3000 and proxies /rpc and /events to the server on port 4005. Application/API traffic should be pointed at the server port, not the Vite port.
- Node.js 22 or newer.
- pnpm 10.8.1. The repo uses Corepack-compatible package metadata.
- Docker, if you want the local Postgres service or container image.
- A Postgres database.
- An upstream LLM-compatible API server, for example a local OpenAI-compatible server on
http://localhost:1234.
Copy the example environment file:
cp .env.example .envSupported environment variables:
DATABASE_URL=postgresql://modash:modash@localhost:5433/modash
UPSTREAM_URL=http://localhost:1234
PORT=4005DATABASE_URLis required by the server and Drizzle migrations.UPSTREAM_URLis the target that proxied requests are forwarded to.PORTcontrols the server listen port and defaults to4005.
The server and Drizzle config explicitly load the repo-root .env when it exists, so commands can be run from either the repo root or package directories. In containers, you can inject environment variables directly without copying a .env file.
Install dependencies:
pnpm installStart Postgres:
docker compose up -d postgresRun migrations:
pnpm db:migrateStart the app:
pnpm devThis starts package dev tasks through Turbo:
- Server:
apps/server, usually available athttp://localhost:4005. - Web dashboard:
apps/web, available athttp://localhost:3000.
Use the proxy by sending LLM API requests to the server, for example http://localhost:4005/v1/chat/completions. The server forwards the request to UPSTREAM_URL/v1/chat/completions, records usage metadata, and emits live dashboard refresh events.
The schema lives in packages/db/src/schema.ts, and migrations are generated into packages/db/drizzle.
Generate a migration after changing the schema:
pnpm db:generateApply migrations:
pnpm db:migrateOpen Drizzle Studio:
pnpm db:studioThe default compose database is:
postgresql://modash:modash@localhost:5433/modashRun all type checks:
pnpm type-checkRun tests:
pnpm testRun lint:
pnpm lintFormat code:
pnpm formatBuild all packages:
pnpm buildThe web build currently emits a Vite chunk-size warning because the dashboard bundle is larger than 500 kB after minification. That warning does not fail the build.
Run only the server:
pnpm --filter @modash/server devRun only the dashboard:
pnpm --filter @modash/web devRun only database tests:
pnpm --filter @modash/db testRun only server tests:
pnpm --filter @modash/server testBuild the image from the repo root:
docker build -t modash:local .The GitHub Actions workflow in .github/workflows/docker.yml builds the image for pull requests and publishes it to GitHub Container Registry on pushes to main, version tags like v1.2.3, and manual workflow dispatches. Published images use these tags:
ghcr.io/bryanmig/modash:latest
ghcr.io/bryanmig/modash:main
ghcr.io/bryanmig/modash:sha-<commit-sha>
ghcr.io/bryanmig/modash:v1.2.3GitHub Actions pushes with the built-in GITHUB_TOKEN, so no Docker Hub credentials are required. If the image package does not appear public after the first push, change the package visibility in GitHub under Packages -> modash -> Package settings -> Change visibility.
Run it against the compose Postgres database and a host-machine upstream service:
docker run --rm \
--name modash \
--network host \
-e DATABASE_URL=postgresql://modash:modash@localhost:5433/modash \
-e UPSTREAM_URL=http://localhost:1234 \
-e PORT=4005 \
modash:localOn Docker Desktop for macOS or Windows, --network host may not behave the same as Linux. Use host.docker.internal for services running on the host:
docker run --rm \
--name modash \
-p 4005:4005 \
-e DATABASE_URL=postgresql://modash:modash@host.docker.internal:5433/modash \
-e UPSTREAM_URL=http://host.docker.internal:1234 \
-e PORT=4005 \
modash:localIf Postgres is running in Docker Compose and the Modash container should join that Compose network, first find the network name:
docker network lsThen run with that network and use the Compose service name postgres:
docker run --rm \
--name modash \
--network modash_default \
-p 4005:4005 \
-e DATABASE_URL=postgresql://modash:modash@postgres:5432/modash \
-e UPSTREAM_URL=http://host.docker.internal:1234 \
-e PORT=4005 \
modash:localThe Docker image starts the server on PORT by running the @modash/server start script. It does not currently serve the built React dashboard as static files from the Node server; run the web app separately in development or serve apps/web/dist with your preferred static host if you need the UI in a containerized deployment.
GET /healthz: Health check endpoint for Kubernetes liveness and readiness probes. Returns{"status":"ok"}.GET /events: SSE stream. Emitsconnected,usage-updated, andrequest-state-updated.POST /rpc: Effect RPC endpoint for dashboard analytics.- Any other path: proxied to the configured upstream.
The shared RPC package defines these dashboard procedures:
UsageSummaryUsageTimeseriesThroughputTimeseriesModelBreakdownRecentRequests
RecentRequests validates page as an integer greater than or equal to 1, and pageSize as an integer between 1 and 100.
- Effect is used for service wiring, typed effects, RPC, PubSub, and SubscriptionRef-based live state.
- Drizzle owns the database schema and query layer.
- The server records usage after upstream responses complete. Streaming responses are parsed from SSE payloads when possible.
- RPC errors are tagged as shared schema values. Database-backed dashboard failures are returned as
{ _tag: "DatabaseError", message }. - The public
@modash/dbpackage surface intentionally exports onlyUsageRepositoryand repository-level types.
If the server fails on startup with a missing database URL, confirm .env exists and contains DATABASE_URL.
If migrations fail to connect, confirm Postgres is running:
docker compose psIf the dashboard loads but shows no data, confirm the web dev server can reach the server through Vite proxy and that the server is running on PORT.
If proxied API calls fail with 502, confirm UPSTREAM_URL points at a reachable upstream server and includes only the origin, not the final request path.
If Docker cannot reach services on the host, use host.docker.internal on Docker Desktop or put the container on the same Docker network as the service.
