ProjectSearch Infrastructure · 2024
Typesense Dashboard
A control plane for Typesense collections, search behavior, migrations and cluster health, in one browser interface instead of curl and Postman.
- Next.js
- TypeScript
- Typesense
- shadcn/ui
- Tailwind CSS
- React Hook Form
- Zod
- Vitest
- Monaco Editor
- Recharts

Search Infrastructure · Project
Typesense Dashboard is a browser workspace for running a Typesense cluster. Collections, documents, schemas, synonyms, overrides, stopwords, presets, search testing and cluster health all live in one place. Before it, any of that meant a tab of API documentation, a curl command and a hand-written JSON blob.
It is a Next.js application with no database of its own. Every screen reads and writes the Typesense API directly, so the dashboard holds no state the cluster does not already hold.
# connected to Typesense 27.1natural language search hidden, needs 29.0GET /collections 4 collectionssearch q=lapotp query_by=name,description1 hit in 3ms, typo corrected to "laptop"alias products -> products_v3, moved atomicallycluster: 3 nodes healthy, snapshot writtenOne control plane
The daily search workflow is not one task. It is a dozen small ones, usually scattered across tools. I wanted them adjacent: build a collection, import documents, tune a synonym, run the query, look at the metrics. Each Typesense resource gets one module under lib/typesense/, so collections.ts, documents.ts, synonyms.ts, search-overrides.ts and cluster-operations.ts each wrap one slice of the API. The pages under app/(app)/ stay thin over those wrappers. The interface keeps the API visible enough for a developer while giving destructive operations context and safer defaults.
Trade-offs I accepted
- The admin API key lives in a cookie. It is httpOnly, marked secure in production, and expires after fourteen days. A setup wizard becomes possible with no user database behind it. Anyone holding that browser profile also holds admin access to the cluster.
- Every wrapper in
lib/typesense/catches its own errors, logs to the console and returnsnull. Pages stay simple. A caller cannot tell an empty result from a failed call, and that is what I would change first. - Monaco Editor is imported dynamically rather than bundled. JSON is the working surface for documents and schemas, and a textarea is the wrong tool for it. The first visit does not pay for an editor it may never open, at the cost of a visible pause when it does.
- The middleware health check runs on every request that is not exempt. Stale configuration surfaces immediately instead of as a failed page. It also adds a round trip to the cluster before anything renders.
Moving a live schema
Changing a live search schema should feel deliberate rather than dangerous, so aliases get a guided flow at /aliases. Create products_v3 with the new schema, load it from /collections/products_v3/documents/import, run the query in the search playground, then move the alias.
await createAlias('products', 'products_v2');
await updateAlias('products', 'products_v3');
The switch is atomic and products_v2 stays exactly where it was, so a rollback is one more updateAlias rather than an incident. The playground holds query, query_by, filters, timing, hit counts and per-result scores in one view, then copies the final request out as a curl command.
Rough edges
The version table in the README disagrees with FEATURE_MIN_VERSIONS in the code, and the code is what actually gates the navigation. Vitest covers version parsing and the resource wrappers under __tests__/, both against a mocked client, so nothing in the suite touches a real server. Cluster operations post straight to /operations/snapshot, /operations/db/compact and /operations/vote through an untyped apiCall escape hatch. Nothing authenticates the dashboard itself. It builds without a running cluster, works against Typesense Cloud or a self-hosted node, ships development and production Dockerfiles, and is MIT licensed.
Next projectProduct Engineering