Skip to content

Typesense Integration ​

File Information ​

  • Path: server/api/typesense/search.post.ts, server/api/typesense/utils.ts, server/utils/searchSchema.ts, shared/searchFields.ts, app/composables/api/useSearchApi.ts, app/composables/core/common/useCommonSearch.ts
  • Purpose: Server-side Typesense search proxy and integration

Overview ​

The portal never talks to Typesense from the browser. The client builds a multi-search request (query, per-collection filter_by, sort, field weights) and posts it to the Nitro route POST /api/typesense/search. The server checks the caller's token against the backend, validates every parameter, narrows the requested fields to what the live schema indexes, runs one Typesense multi-search with the server-only API key, signs S3 paths in the hits and returns one result per collection.

This page is the map. Exact rules live in Typesense Search API, Typesense utils, Search fields and useCommonSearch.

Architecture ​

System Components ​

PieceWhereRole
CollectionsTypesensedigital_assets (assets), dam_collections (collages), digital_assets_categories (folders). Nothing else is searchable (ALLOWED_COLLECTIONS).
Search routeserver/api/typesense/search.post.tsAuth check, validation, request assembly, response shaping
Client and helpersserver/api/typesense/utils.tsLazy Typesense client, performMultiSearch, S3 URL signing
Field specsshared/searchFields.ts, server/utils/searchSchema.tsquery_by / weights / infix per collection, _eval relevance band, schema narrowing
Filter builderuseCommonSearchTurns filter chips into filter_by and adds the instance visibility clause
Page logicuseSearch, useSearchBar, useFolderDetails, useCollageDetailsBuild and send requests, paginate, merge results

Key Features ​

1. Server-Side Search Proxy ​

  • The Typesense API key is read from runtime config on the server only.
  • The Authorization header is required (401 if missing). The server calls the backend user endpoint with it and requires an entry in accessibleInstances whose workspace_id equals workspace_id_filter; otherwise 401. Missing request, workspace_id_filter or commonSearchParams is a 400, checked after auth.
  • The workspace check only gates access. The server adds no workspace or instance filter of its own; scoping comes from the client's instance_visibility clause.
  • One request can name several of the three allowed collections; any other name is a 400 (Collection "<name>" is not searchable.).
  • query_by, query_by_weights and infix may be one value for all collections or a per-collection map; filterQuery and sort_by can also be per collection.
  • The search page only queries the active tab's collection. Folders and collages are skipped when an asset-only filter or asset-type filter is active (see useCommonSearch).

3. Advanced Filtering ​

Filters are Typesense filter_by strings built on the client by useCommonSearch: tags, file type, orientation, date added / last modified, permission (public or not for this portal), custom fields (portal_custom_fields) and the instance visibility clause (instance_visibility:=<brand id> && instance_visibility:!=0). The server passes filterQuery[collection] through unchanged.

4. Sorting ​

  • Default asset sort is modified_at:desc. Sortable list columns are Name, Date Modified, Date Added, File Size and File Type; for folders and collages, name maps to folder_name / collection_name and size/type fall back to modified_at.
  • With a text query the sort is prefixed with _text_match:desc, and the client prepends an _eval relevance band. The server rebuilds the band from the schema only if it names a field that collection does not index.
  • Each clause must match field:asc|desc; malformed sorts are a 400.

5. Pagination ​

  • per_page and page come from commonSearchParams; infinite scroll requests the next page for the active tab only and appends hits.
  • Each collection's Typesense result (hits, found, page, highlights) is returned as is under data.

File Structure ​

Core Files ​

Server API Routes ​

  • server/api/typesense/search.post.ts - auth, validation (collections, text_match_type, query_by, weights 0-127, infix always|fallback|off, sort_by), per-collection request assembly, error mapping.
  • server/api/typesense/utils.ts - getTypesenseClient (lazy, so the server starts without Typesense env), performMultiSearch, signS3PathsInResults / getCachedSignedUrl (24 h presigned URLs, 23 h in-memory cache).

Composables ​

  • app/composables/api/useSearchApi.ts - searchAssets(payload) posts to /api/typesense/search.
  • app/composables/core/common/useCommonSearch.ts - filter strings and per-collection filter map.

Stores ​

  • app/stores/search.ts - applied filter lists (search page, folder, collage), cached search data.

Configuration ​

Environment Variables ​

Read in nuxt.config.ts into runtime config. Values live in .env and are not documented here.

VariableRuntime keyRequired
TYPESENSE_HOSTtypesenseHostyes
TYPESENSE_PORTtypesensePortyes, 1-65535
TYPESENSE_PROTOCOLtypesenseProtocolyes
TYPESENSE_API_KEYtypesenseApiKeyyes
TYPESENSE_CONNECTION_TIMEOUTtypesenseConnectionTimeoutno, default 10 s
API_BASE_URLpublic.apiBaseUrlyes (token check); missing gives 500

Server Configuration ​

getTypesenseConfig() in utils.ts:

typescript
{
  nodes: [{ host, port, protocol }],
  apiKey,
  connectionTimeoutSeconds, // TYPESENSE_CONNECTION_TIMEOUT or 10
  retryIntervalSeconds: 0.1,
  numRetries: 3,
  healthcheckIntervalSeconds: 30,
}

A missing host, port, protocol or key throws Typesense is not configured. Missing: ... on first use, not at startup.

Search Flow ​

Search Execution ​

  1. useSearch.fetchResults(flag) builds filters, sort and field params for the active tab.
  2. useSearchApi.searchAssets posts to /api/typesense/search with the user's token.
  3. The server verifies the token and workspace, then validates the body.
  4. Requested fields are narrowed to the live schema (resolveRequestedSearchFields), so a field that is not indexed yet drops out instead of failing.
  5. Each collection gets fixed params: lowercased q (* stays a wildcard), prefix: true, num_typos: 0, typo_tokens_threshold: 1, prioritize_exact_match and prioritize_token_position true, text_match_type default max_score.
  6. One multi_search call; results are keyed by collection and S3 paths are signed.
  7. The client stores hits per tab and attaches search_matches for the Matched In column.

Filter Building ​

See useCommonSearch for the rule table.

API Endpoints ​

  • Endpoint: POST /api/typesense/search
  • Request:
    typescript
    {
      request: {
        q?: string,                     // default "*"
        collections: string[],          // subset of ALLOWED_COLLECTIONS
        sort_by?: string | { [collection: string]: string },
        include_fields?: { [collection: string]: string[] },
        group_by?: string,
        group_limit?: number,
      },
      commonSearchParams: {
        query_by: string | { [collection: string]: string },
        query_by_weights?: string | { [collection: string]: string | Record<string, number> },
        infix?: string | { [collection: string]: string | Record<string, string> },
        per_page: number,
        page: number,
        sort_by?: string,
        text_match_type?: 'max_score' | 'max_weight' | 'sum_score',
      },
      workspace_id_filter: string,
      filterQuery?: { [collection: string]: string },
    }
  • Response:
    typescript
    {
      [collection: string]: { data: /* Typesense search result: hits, found, page, ... */ }
    }
  • Errors: 401 (no header, invalid token, workspace not accessible), 400 (missing params, bad collection, text_match_type, query_by, weights, infix or sort_by), 500 (missing API_BASE_URL, Typesense failures). Full table in Typesense Search API.

Security Features ​

  1. Server-side API key: only the Nitro server holds TYPESENSE_API_KEY.
  2. Token and workspace check: every request is checked against the backend user endpoint and the caller's accessibleInstances.
  3. Allow-listed collections and validated parameters: field names, weights, infix modes and sort clauses are pattern-checked before reaching Typesense.
  4. Portal scoping is client-built: the instance_visibility clause comes from the client filter; the server does not add it.

Performance Optimizations ​

  1. Only the active tab's collection is queried; folder item counts load afterwards, three at a time.
  2. Schema narrowing avoids failed requests when the index lags the field specs.
  3. Signed S3 URLs are cached in memory for 23 hours (URLs are valid 24 hours).
  4. The Typesense client is created once and reused.