A production-quality error tracking and monitoring server for JavaScript and React applications. Receives error events over HTTP, groups them into issues using fingerprinting, and stores them in PostgreSQL.
- Event Ingestion: Receive JavaScript/React error events via REST API
- Smart Grouping: Automatically group similar errors using fingerprint algorithm
- Issue Management: Track, resolve, and ignore issues
- Rate Limiting: Per-project rate limiting (in-memory for v1)
- Authentication: Project-based auth with hashed secrets
- CORS Support: Configured for frontend integration
- Structured Logging: JSON logging with request tracing
- Docker - Required for PostgreSQL database
- Python 3.12+ - For local development
# 1. Clone and setup
git clone <repository-url>
cd errflow-server
# 2. Bootstrap Python environment
./scripts/bootstrap.sh
# 3. Activate virtual environment
source .venv/bin/activate
# 4. Start PostgreSQL
./infrastructure/up.sh
# 5. Run database migrations
alembic upgrade head
# 6. Seed demo project
python -m scripts.seed
# 7. Start the API server
uvicorn app.main:app --reloadAPI available at: http://localhost:8000 API docs at: http://localhost:8000/docs
# Start everything
docker compose up -d
# Run migrations
docker compose --profile migrate up migrate
# Seed demo project
docker compose --profile seed up seedAll configuration via environment variables. Copy env.example to .env and customize:
| Variable | Default | Description |
|---|---|---|
ERRFLOW_DB_HOST |
localhost | PostgreSQL host |
ERRFLOW_DB_PORT |
5432 | PostgreSQL port |
ERRFLOW_DB_NAME |
errflow | Database name |
ERRFLOW_DB_USER |
errflow | Database user |
ERRFLOW_DB_PASSWORD |
errflow_secret | Database password |
ERRFLOW_CORS_ORIGINS |
http://localhost:5173 | Allowed CORS origins (comma-separated) |
ERRFLOW_RATE_LIMIT_REQUESTS |
100 | Max requests per window |
ERRFLOW_RATE_LIMIT_WINDOW_SECONDS |
60 | Rate limit window |
ERRFLOW_LOG_LEVEL |
INFO | Logging level |
ERRFLOW_LOG_FORMAT |
json | Log format (json/text) |
All API endpoints (except /health) require authentication headers:
X-Project-Key: <project_key>
Authorization: Bearer <project_secret>
Demo credentials:
- Project Key:
demo-web - Project Secret:
demo-secret
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| POST | /api/v1/events |
Ingest error event |
| GET | /api/v1/issues |
List issues |
| GET | /api/v1/issues/{id} |
Get issue details |
| PATCH | /api/v1/issues/{id} |
Update issue status |
| GET | /api/v1/issues/{id}/events |
List events for issue |
The API accepts exception.stack as a string (standard JS Error.stack):
curl -X POST http://localhost:8000/api/v1/events \
-H "Content-Type: application/json" \
-H "X-Project-Key: demo-web" \
-H "Authorization: Bearer demo-secret" \
-d '{
"exception": {
"name": "TypeError",
"message": "Cannot read properties of undefined (reading '\''map'\'')",
"stack": "TypeError: Cannot read properties of undefined\n at UserList.render (http://localhost:5173/src/UserList.tsx:42:15)\n at renderWithHooks (react-dom.js:1234:22)"
},
"level": "error",
"url": "http://localhost:5173/users",
"user_agent": "Mozilla/5.0 Chrome/120.0.0.0",
"timestamp": null,
"tags": {"version": "1.0.0"},
"breadcrumbs": [
{"type": "navigation", "category": "route", "message": "/users"}
],
"user": {"id": "user-123", "email": null},
"environment": "production"
}'Response:
{
"event_id": 1,
"issue_id": 1,
"is_new_issue": true
}Accepted stack formats:
- String (recommended): raw JS
Error.stack - List of strings: joined with
\n - List of objects:
{filename, function, lineno, colno}(backward compat)
Accepted timestamp formats:
- ISO 8601 string:
"2025-01-01T12:00:00Z" - Unix timestamp (seconds):
1735689600 - Unix timestamp (milliseconds):
1735689600000 nullor missing: uses server time
curl http://localhost:8000/api/v1/issues \
-H "X-Project-Key: demo-web" \
-H "Authorization: Bearer demo-secret"curl -X PATCH http://localhost:8000/api/v1/issues/1 \
-H "Content-Type: application/json" \
-H "X-Project-Key: demo-web" \
-H "Authorization: Bearer demo-secret" \
-d '{"status": "resolved"}'# Start PostgreSQL
./infrastructure/up.sh
# Stop PostgreSQL (preserves data)
./infrastructure/down.sh
# Reset database (deletes all data)
./infrastructure/reset.sh
# View PostgreSQL logs
./infrastructure/logs.shA simple React viewer for browsing errors:
cd frontend
npm install
npm run devOpens at http://localhost:5175
Features:
- View all issues with status filter
- Click issue to see details and stack traces
- Auto-refresh every 10 seconds
- Dark theme with Tailwind CSS
errflow-server/
├── app/ # FastAPI backend
│ ├── main.py # Application entry
│ ├── settings.py # Configuration
│ ├── db.py # Database connection
│ ├── models.py # SQLAlchemy models
│ ├── schemas.py # Pydantic schemas
│ ├── auth.py # Authentication
│ ├── rate_limit.py # Rate limiting
│ ├── fingerprinting.py # Error grouping
│ ├── routers/ # API endpoints
│ └── middleware/ # Request middleware
├── frontend/ # React viewer (Vite + Tailwind)
│ ├── src/
│ │ ├── App.jsx # Main component
│ │ └── api.js # API client
│ └── package.json
├── alembic/ # Database migrations
├── infrastructure/ # Docker/DB scripts
├── scripts/ # Dev scripts
├── docker-compose.yml # Full stack deployment
├── Dockerfile # API container
└── requirements.txt # Python dependencies
The server is configured to accept requests from http://localhost:5173 by default (Vite dev server). For production, set the ERRFLOW_CORS_ORIGINS environment variable:
ERRFLOW_CORS_ORIGINS=https://app.example.com,https://staging.example.comEvents are grouped into issues using a fingerprint computed from:
- Exception name - e.g.,
TypeError - Exception message - normalized to remove dynamic values
- Stack trace - normalized to remove line numbers, bundle hashes, and query strings
This ensures similar errors are grouped together even if they occur at different times or with slightly different stack traces.
projects
├── id (pk)
├── project_key (unique)
├── project_secret_hash
├── name
└── created_at
issues
├── id (pk)
├── project_id (fk)
├── fingerprint (indexed)
├── title
├── level (error/fatal/warn)
├── first_seen
├── last_seen
├── count
└── status (open/ignored/resolved)
events
├── id (pk)
├── issue_id (fk)
├── received_at
├── client_timestamp
├── payload_json (jsonb)
└── normalized_stack
This project is structured to support CI/CD pipelines:
- Testing: Add pytest tests and run with
pytest --cov - Linting: Use
ruff check .andmypy . - Docker Build: Multi-stage Dockerfile for efficient builds
- Migrations: Use
alembic upgrade headin deployment - Health Checks:
/healthendpoint for readiness probes - Secrets: Use environment variables or secret managers
Example GitHub Actions workflow:
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements-dev.txt
- run: ruff check .
- run: mypy .
- run: pytest --covMIT