An open-source reference / demo application built on top of the Search Router API. This repository is not the Search Router API itself, and it is not an MCP server — it is a self-contained metasearch service (FastAPI + Jinja UI) that shows how to integrate the API in a production-shaped app: pluggable backend adapters, deterministic mocks for keyless local dev, Redis caching, circuit breaker, RTL UI, and a small demo ads cabinet.
The hosted API lives at search-router.com. This repo is the recommended starting point if you want a working end-to-end example you can fork.
What this repo is — and isn't
Is:
- A working reference UI + service on top of the Search Router API.
- A template you can fork: drop in your own backend adapter, ship.
- A keyless demo:
docker compose upand you have a real-looking search UI backed by deterministic mocks.
Isn't:
- The Search Router API itself — that's a hosted service at search-router.com.
- An MCP server. (If you're looking for the Search Router MCP, see search-router.com.)
- A production drop-in. Read docs/deployment.md before exposing it.
Features
- Use cases: grounding LLM answers with fresh web data, agent tool calls, and building search/RAG-adjacent UIs. The Search Router API returns titles, URLs, and snippets across multiple search engines; full-page Infocontexts are on the upstream roadmap.
- Backend: Python 3.12+, FastAPI, Pydantic v2, httpx, defusedxml.
- Frontend: Server-rendered Jinja2 with a small modern design system —
light/dark, logical CSS properties, full RTL coverage for
ar,he,fa,ur(and any other RTL tag you add). - Storage: Optional Redis cache; degrades to a
NullCachewhen Redis is not configured. - No keys required: Missing credentials transparently swap real adapters
for mock implementations, so
docker compose upproduces a fully working, beautiful demo out of the box. - Pluggable backends: Drop in your own provider via the
search_service.backendsentry point — no fork required. - Production-minded: Circuit breaker, structured JSON logging, security headers, CSRF, rate limiting, admin-token-gated health/introspection.
- Demo ads cabinet: Included
app/ads/package (auction, auth, storage, throttling) illustrates how a monetization layer could plug into a search UI. It's a demo surface, not part of the Search Router API.
Quick start
git clone https://github.com/search-router/simple-search.git cd simple-search cp .env.example .env docker compose up --build # open http://localhost:8000/
To run without Docker:
python -m venv .venv && source .venv/bin/activate pip install -e ".[dev,redis]" uvicorn app.main:app --reload # open http://localhost:8000/
The home page works immediately (mock-backed). Set SEARCH_ROUTER_API_KEY in
.env to switch the Search Router adapter to its real upstream.
Getting a Search Router API key
The bundled adapter calls the Search Router API at
https://search-router.com/api/search (header X-API-Key). The API returns
search results — titles, URLs, snippets, and image metadata — suitable for
grounding LLMs and powering search UIs.
search-router.com gives you:
- 2000 free credits to start.
- When your balance drops below 500, top it up with another 2,000 credits for free — unlimited refills while the promotion is active.
To obtain a key:
-
Sign up at https://search-router.com.
-
Open your account dashboard and create an API key.
-
Copy the key into your local
.env:SEARCH_ROUTER_API_KEY=sr_live_xxxxxxxxxxxxxxxxxxxxxxxx
-
Restart the app (
docker compose up --buildoruvicorn …). The/api/v1/backendsendpoint will now reportsearch_routeras ready and queries will hit the real upstream.
Without a key, the adapter transparently falls back to the deterministic mock backend — you can develop and demo the UI without signing up.
Configuration
Configuration lives in .env (secrets and environment-level knobs) and
config.yaml (declarative backend wiring, feature flags). See
.env.example for the full list with comments.
| Variable | Required | Default | Purpose |
|---|---|---|---|
APP_ENV |
no | dev |
dev or prod; tightens defaults in prod. |
APP_CONFIG_FILE |
no | config.yaml |
Path to the declarative config file. |
LOG_LEVEL |
no | INFO |
Standard Python logging levels. |
SEARCH_ROUTER_API_KEY |
no | (empty) | Real Search Router credentials — get one at search-router.com. Empty → adapter falls back to mock. |
REDIS_URL |
no | (empty) | Optional cache. Empty → NullCache. |
ADMIN_TOKEN |
prod | (empty) | Required to call /api/v1/health and /api/v1/backends. |
SESSION_SECRET |
prod | (empty) | Signs ads-cabinet session cookies. Empty in dev → ephemeral per-process. |
See docs/deployment.md for the hardening checklist
before running with APP_ENV=prod.
API
| Method | Path | Body |
|---|---|---|
| POST | /api/v1/search/web |
{ q, backend, language, region, page, limit, … } |
| POST | /api/v1/search/images |
same shape + image_filters |
| GET | /api/v1/backends |
introspection list |
| GET | /api/v1/health |
service + redis + per-backend status |
| GET | /docs |
OpenAPI / Swagger UI |
Example:
curl -s http://localhost:8000/api/v1/search/web \ -H 'Content-Type: application/json' \ -d '{"q":"python async search","limit":5}' | jq
Full schema: docs/api.md.
UI
GET /— search hero, segmented Web / Images toggle, locale picker.GET /search?q=…&type=web— web results with breadcrumb cards, pagination.GET /search?q=…&type=images— image grid with<dialog>-based lightbox.- Light/dark theme via
prefers-color-schemeplus a manual toggle. - Full RTL support: pass
?ui_locale=ar(orhe,fa,ur) and the entire layout mirrors automatically. Result text usesdir="auto"so mixed-script snippets (e.g.python مكتبة البحث) render correctly inside an RTL page.
Project layout
.
├── app/
│ ├── api/ FastAPI routers (v1)
│ ├── backends/ BaseBackend + search-router adapter + mocks
│ ├── core/ config, cache, circuit breaker, i18n, logging, security
│ ├── search/ schemas, registry, router, normalizer, ranking
│ ├── ads/ ads cabinet (auth, storage, auction)
│ ├── ui/ Jinja templates, static assets, translations
│ └── main.py app factory
├── tests/
│ ├── unit/ schemas, normalizer, adapter, breaker, cache
│ ├── integration/ end-to-end API round-trips through mock backends
│ └── ui/ server-rendered template assertions (incl. RTL)
├── docs/ architecture, adapters, search-router, i18n, api, deployment
├── config.yaml declarative backend and feature wiring
├── Dockerfile
└── docker-compose.yml
Adding a new backend
See docs/backend-adapters.md. In short:
- Subclass
app.backends.base.BaseBackend. - Implement
search_web,search_images, pluscapabilities(). - Either:
- Register it in
config.yamlundersearch.backends.<name>and add a factory entry inapp/search/registry.py; or - Expose it via the
search_service.backendsentry point group from a separate Python distribution — the registry will pick it up automatically.
- Register it in
No public route or response schema needs to change.
Testing
pytest -q
ruff check .
mypy app/Unit tests cover schemas, i18n, normalizer, the Search Router adapter (with
httpx.MockTransport), the circuit breaker, and the cache key builder.
Integration tests round-trip through the mock backends to validate the API and
UI rendering, including RTL.
Documentation
- docs/architecture.md — request lifecycle, components.
- docs/backend-adapters.md — how to add a provider.
- docs/search-router.md — the bundled adapter in detail.
- docs/i18n-rtl.md — translations and RTL conventions.
- docs/api.md — request/response schemas.
- docs/deployment.md — running in production.
License
MIT.



