Projects

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
Typesense Dashboard interface

Search Infrastructure · Project

Chapter
Web products
Year
Availability
Open source
Last commit

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 written
Typesense Dashboard in use

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

Version aware navigation

Typesense gains capabilities between server releases, and showing every control to every user would create dead ends. The dashboard reads /debug, which returns the running version, and lib/typesense/version.ts compares it against a feature map.

export const FEATURE_MIN_VERSIONS: Record<string, string> = {
  stopwords: '26.0',
  conversationalSearch: '26.0',
  stemmingDictionaries: '28.0',
  naturalLanguageSearch: '29.0',
  synonymsGlobal: '30.0',
  diversifyResults: '30.0',
};

parseVersion handles both the old 0.25.2 scheme and the newer 27.1 one, so an old cluster still compares correctly. TypesenseVersionProvider puts hasFeature into context and the navigation hides what the server cannot do. When detection fails the provider is deliberately permissive and shows everything.

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 returns null. 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

Yoink

View project