Skip to content
arrow_back Back to writings
08 MIN READ · 455 words
Listen to Article
click play to listen

Sub-Second Search Latency: Integrating Typesense with Node.js

How we designed and integrated a low-latency, typos-tolerant search engine into a fintech application using Node.js, GraphQL, and Typesense.

NODEJS TYPESENSE SEARCH SYSTEMS

Fast search autocomplete is no longer a luxury—it is a baseline user expectation. In data-heavy systems, hitting a traditional relational database (like PostgreSQL) for fuzzy search query matching will quickly exhaust connection pools and introduce noticeable latency.

To solve this in a corporate banking onboarding workflow, we integrated Typesense, an open-source, in-memory search engine, with a Node.js and GraphQL backend. Here is how we achieved sub-100ms search latency across millions of records.

Why Typesense?

While Elasticsearch is the industry giant, it is resource-intensive and requires complex configuration. Typesense is built in C++, loads its index entirely into RAM, and is designed specifically for instant, typo-tolerant searchout-of-the-box.


1. Schema Definition & Indexing in Node.js

First, we define our collection schema and initialize the Typesense client in our Node.js environment:

const Typesense = require('typesense');

const client = new Typesense.Client({
  nodes: [{
    host: 'localhost',
    port: '8108',
    protocol: 'http'
  }],
  apiKey: 'xyz-secret-key-abc',
  connectionTimeoutSeconds: 2
});

// Defining a schema for Corporate Clients
const clientsSchema = {
  name: 'clients',
  fields: [
    { name: 'companyName', type: 'string' },
    { name: 'registrationId', type: 'string', facet: true },
    { name: 'country', type: 'string', facet: true },
    { name: 'annualRevenue', type: 'int32' }
  ],
  default_sorting_field: 'annualRevenue'
};

async function initializeSearchIndex() {
  try {
    await client.collections().create(clientsSchema);
    console.log('Typesense index initialized successfully.');
  } catch (err) {
    console.log('Collection already exists or initialization failed.', err);
  }
}

2. Syncing Postgres Data with Typesense

To maintain consistency, we used a write-through pattern in our Node.js repository layer. When a client record is updated or created in Postgres, we synchronously index the change in Typesense:

async function createClientRecord(dbConnection, clientData) {
  // 1. Write to Postgres
  const newClient = await dbConnection.query(
    'INSERT INTO clients(company_name, reg_id, country, revenue) VALUES($1, $2, $3, $4) RETURNING *',
    [clientData.name, clientData.regId, clientData.country, clientData.revenue]
  );
  
  // 2. Index in Typesense
  await client.collections('clients').documents().create({
    id: newClient.rows[0].id.toString(),
    companyName: newClient.rows[0].company_name,
    registrationId: newClient.rows[0].reg_id,
    country: newClient.rows[0].country,
    annualRevenue: newClient.rows[0].revenue
  });

  return newClient.rows[0];
}

3. Querying Search with Typo Tolerance

In our GraphQL resolver, we query Typesense using its built-in search parameters, specifying query weights and typo tolerance:

const resolvers = {
  Query: {
    searchClients: async (_, { query, filterCountry }) => {
      const searchParameters = {
        q: query,
        query_by: 'companyName,registrationId',
        filter_by: filterCountry ? `country:=${filterCountry}` : '',
        num_typos: 2,
        sort_by: 'annualRevenue:desc'
      };

      const searchResults = await client
        .collections('clients')
        .documents()
        .search(searchParameters);

      return searchResults.hits.map(hit => ({
        id: hit.document.id,
        companyName: hit.document.companyName,
        country: hit.document.country,
        revenue: hit.document.annualRevenue
      }));
    }
  }
};

Performance Results

By introducing Typesense:

  • Average search API latency dropped from 850ms (SQL queries matching pattern strings) to 12ms.
  • Fuzzy-search typo tolerance immediately improved search matching rates by 42%.
  • Relational database CPU load dropped by 30%, freeing up connection pools for transaction records processing.