Build Site Search with Lyra and TypeScript

Implement fast, typo-tolerant client-side search for your web applications

Introduction to Lyra Search

Lyra is a fast, in-memory, typo-tolerant, full-text search engine written entirely in TypeScript. This guide covers implementing site search using Lyra with TypeScript, suitable for developers building modern web applications that require performant, client-side search capabilities.

What is Lyra?

Lyra is a TypeScript-native full-text search engine that operates entirely in-memory. It provides:

  • Instant search results without network latency
  • Typo tolerance that handles spelling mistakes gracefully
  • Full TypeScript support with complete type inference
  • Runtime agnostic design working in browser, Node.js, and edge environments
  • No external dependencies for simplified deployment

According to the official Lyra documentation, the library is designed to be lightweight, fast, and easy to integrate into existing projects without additional infrastructure overhead.

Implementing robust search functionality is a core component of modern web development services, enhancing user experience and content discoverability across your digital presence.

Why Choose Client-Side Search?

Reduced Server Load

Search runs entirely client-side, eliminating server infrastructure costs and reducing latency for your users.

Instant Results

In-memory architecture provides sub-millisecond query times for snappy, responsive user experiences.

Works Offline

Client-side indexing enables full search functionality without requiring network connectivity.

Privacy Friendly

Search data stays entirely on the user's device, making it ideal for sensitive content.

Setting Up Your Project

Let's start by creating a new TypeScript project and installing Lyra. This setup provides the foundation for implementing performant search in your application.

Initialize Project
1mkdir lyra-search-demo2cd lyra-search-demo3npm init -y4npm install lyra typescript @types/node --save-dev5npx tsc --init

TypeScript Configuration

Configure your tsconfig.json for optimal Lyra support with strict mode enabled:

{
 "compilerOptions": {
 "target": "ES2020",
 "module": "ESNext",
 "moduleResolution": "bundler",
 "strict": true,
 "esModuleInterop": true,
 "skipLibCheck": true,
 "forceConsistentCasingInFileNames": true,
 "outDir": "./dist",
 "declaration": true
 },
 "include": ["src/**/*"],
 "exclude": ["node_modules", "dist"]
}

For advanced TypeScript patterns in Lyra schemas, see our guide on Advanced TypeScript.

Project Structure

Organize your project for maintainability and scalability:

lyra-search-demo/
├── src/
│ ├── index.ts # Main entry point
│ ├── schema.ts # Lyra schema definitions
│ └── data/
│ └── documents.json # Sample data
├── dist/ # Compiled output
├── package.json
└── tsconfig.json

A well-organized project structure makes it easier to maintain your search implementation as your application grows.

Defining the Search Schema

The schema defines the structure of documents that Lyra will index. Each field can be configured for searchability, filtering, and relevance boosting, ensuring your search results match user intent effectively.

import { schema } from 'lyra';

export const searchSchema = schema({
 document: {
 id: 'string',
 title: 'string',
 content: 'string',
 author: 'string',
 tags: 'string[]',
 publishedAt: 'datetime',
 category: 'string'
 },
 store: true
});

TypeScript generics play a crucial role in ensuring type safety throughout your search implementation. Learn more about TypeScript generics used in Lyra schemas.

Lyra Field Configuration Options
TypeDescriptionUse Case
stringText field with configurable tokenizerTitles, content, descriptions
numberNumeric values for range queriesPrices, dates as numbers
booleanBinary true/false valuesFlags, status indicators
arrayMulti-value fieldsTags, categories, keywords
datetimeDate/time values with temporal filteringPublication dates, timestamps

Indexing Documents

Once the schema is defined, you can create an index and populate it with documents. Lyra's in-memory design ensures fast indexing even for large datasets, making it suitable for applications with thousands of searchable documents.

import { create, insert } from 'lyra';
import { searchSchema } from './schema';

const db = await create({
 schema: searchSchema
});

const documents = [
 {
 id: '1',
 title: 'Getting Started with TypeScript',
 content: 'Learn the basics of TypeScript and how to set up your development environment.',
 author: 'Jane Doe',
 tags: ['typescript', 'javascript'],
 publishedAt: new Date('2024-01-15'),
 category: 'programming'
 },
 {
 id: '2',
 title: 'Building React Applications',
 content: 'A comprehensive guide to building modern React applications with hooks and context.',
 author: 'John Smith',
 tags: ['react', 'javascript', 'frontend'],
 publishedAt: new Date('2024-02-20'),
 category: 'programming'
 }
];

for (const doc of documents) {
 await insert(db, doc);
}

console.log(`Indexed ${documents.length} documents`);

For a deeper understanding of indexing strategies, see our guide on Data Indexing Strategies.

Bulk Insertion

For larger datasets, use batch insertion with progress tracking to provide feedback during indexing:

import { insertMultiple } from 'lyra';

await insertMultiple(db, documents, {
 batchSize: 100,
 onProgress: (processed) => {
 console.log(`Indexed ${processed} documents`);
 }
});

This approach is essential when indexing thousands of documents, as it prevents memory issues and keeps your application responsive during the indexing process.

Implementing Search Functionality

Lyra provides a powerful search API with support for fuzzy matching, field boosting, filtering, and pagination. These features work together to deliver relevant results that match user intent.

import { search } from 'lyra';

const result = await search(db, {
 term: 'typescript',
 properties: ['title', 'content'],
 boost: { title: 2 }, // Title matches are more relevant
 tolerance: 2, // Allow up to 2 character typos
 limit: 10 // Return max 10 results
});

console.log(`Found ${result.count} results:`);
result.hits.forEach((hit) => {
 console.log(`- ${hit.title} (score: ${hit.score})`);
});

For search performance optimization techniques, see our guide on Optimizing React Performance.

Filtering Results

Combine full-text search with structured filters to help users narrow down results:

const filtered = await search(db, {
 term: 'tutorial',
 where: {
 category: 'programming',
 author: 'Jane Doe'
 },
 range: {
 publishedAt: {
 gte: new Date('2024-01-01'),
 lte: new Date('2024-12-31')
 }
 }
});

Filtering allows users to combine keyword searches with structured data queries, creating powerful faceted search experiences.

Building a React Search Component

Integrate Lyra search into a React application with a custom hook for state management and reusable components. This pattern keeps your search logic separate from your UI components.

import { useState, useEffect } from 'react';
import { search } from 'lyra';
import { LyraDatabase } from '@lyra/core';

interface SearchResult {
 id: string;
 title: string;
 content: string;
 author: string;
}

export function useLyraSearch(index: LyraDatabase) {
 const [query, setQuery] = useState('');
 const [results, setResults] = useState<SearchResult[]>([]);
 const [loading, setLoading] = useState(false);

 useEffect(() => {
 if (!query.trim()) {
 setResults([]);
 return;
 }

 const timer = setTimeout(async () => {
 setLoading(true);
 const result = await search(index, {
 term: query,
 limit: 20
 });
 setResults(result.hits as SearchResult[]);
 setLoading(false);
 }, 300);

 return () => clearTimeout(timer);
 }, [query, index]);

 return { query, setQuery, results, loading };
}

To understand where to run Lyra in your application, see our guide on React Server Components.

SearchComponent.tsx
1import { useLyraSearch } from './hooks/useLyraSearch';2import { db } from './lyra';3 4export function SearchComponent() {5 const { query, setQuery, results, loading } = useLyraSearch(db);6 7 return (8 <div className="search-container">9 <input10 type="text"11 value={query}12 onChange={(e) => setQuery(e.target.value)}13 placeholder="Search articles..."14 className="search-input"15 />16 17 {loading && <span className="loading-spinner">Searching...</span>}18 19 {results.length > 0 && (20 <ul className="search-results">21 {results.map((result) => (22 <li key={result.id} className="search-result">23 <h3>{result.title}</h3>24 <p>{result.content.substring(0, 100)}...</p>25 </li>26 ))}27 </ul>28 )}29 30 {query && !loading && results.length === 0 && (31 <p className="no-results">No results found for "{query}"</p>32 )}33 </div>34 );35}

Performance Optimization

Optimize your Lyra search implementation for production use with these techniques. Even though Lyra is already fast, these optimizations ensure the best possible user experience at scale.

  • Limit result sets: Always use the limit parameter to prevent over-fetching and reduce rendering overhead
  • Select properties: Use the properties option to return only needed fields, reducing memory usage
  • Cache queries: Memoize frequent searches with React useMemo to avoid redundant computations
  • Web Workers: Move indexing to background threads to avoid blocking the main UI thread
  • Pre-serialize indexes: Build indexes during the build process for faster initial load times

Effective search implementation contributes to better site performance and user engagement, key factors considered in our SEO services approach.

Persistence and Storage

Save search indexes to local storage for faster subsequent loads:

import { saveIndexToDisk, loadIndexFromDisk } from 'lyra';

// Save to localStorage (for small indexes)
const serialized = await db.serialize();
localStorage.setItem('search-index', serialized);

// Save to IndexedDB (for larger indexes)
const idb = await openIndexedDB('search-index');
await saveToIndexedDB(idb, 'lyra-index', serialized);

For a comparison of client-side storage options, see our guide on Client-Side Storage Solutions.

Deployment Considerations

Deploy Lyra to edge environments for global low-latency search capabilities. Cloudflare Workers provides an excellent platform for serving search functionality close to your users.

import { create, search } from 'lyra';

interface Env {
 LYRA_INDEX: KVNamespace;
}

export default {
 async fetch(request: Request, env: Env): Promise<Response> {
 const url = new URL(request.url);
 
 if (url.pathname === '/search') {
 const query = url.searchParams.get('q');
 if (!query) {
 return new Response('Missing query', { status: 400 });
 }
 
 const indexData = await env.LYRA_INDEX.get('search-index');
 const db = await create({ schema: searchSchema });
 
 if (indexData) {
 await db.import(indexData as string);
 }
 
 const results = await search(db, { term: query });
 return new Response(JSON.stringify(results), {
 headers: { 'Content-Type': 'application/json' }
 });
 }
 
 return new Response('Not Found', { status: 404 });
 }
};

For detailed edge deployment guidance, see our guide on Cloudflare Workers.

Lyra vs Alternative Search Solutions
FeatureLyraLunr.jsFlexSearch
TypeScript SupportNativeRequires typesPartial
Bundle Size~10KB~25KB~15KB
Typo ToleranceBuilt-inPluginBuilt-in
Edge ReadyYesNoLimited
Last UpdateActiveMaintenanceActive

When to Choose Lyra

Lyra is an excellent choice for new TypeScript projects requiring client-side search, especially when edge deployment is needed, bundle size is a priority, or teams want strong TypeScript integration throughout their codebase.

For advanced semantic search capabilities combining keyword matching with AI-powered understanding, explore our guides on Building AI-Powered Search and RAG: Retrieval Augmented Generation.

Conclusion and Next Steps

Lyra provides a powerful, lightweight solution for implementing client-side search in TypeScript applications. Its in-memory architecture delivers instant results while maintaining type safety throughout your codebase.

Key Takeaways

  • Setup is straightforward with minimal configuration required for basic search functionality
  • TypeScript schema ensures type-safe document definitions and IDE support
  • Performance is excellent for datasets under 100,000 documents
  • Edge deployment extends capabilities to global audiences with low latency

Next Steps

  • Explore hybrid search combining Lyra with AI embeddings for semantic search capabilities
  • Implement autocomplete suggestions as users type for better discoverability
  • Add search analytics to understand user behavior and popular queries
  • Build faceted search interfaces for large datasets with multiple categorization options

For a broader understanding of AI features in web applications, see our guide on AI Integration Patterns. When you're ready to implement intelligent search solutions for your business, our AI automation services can help you design and deploy custom search experiences tailored to your specific needs.

Frequently Asked Questions

Ready to Add Intelligent Search to Your Application?

Our team can help you implement Lyra or design a custom search solution tailored to your specific needs and use cases.

Sources

  1. Lyra Official Documentation - Official documentation for the Lyra search engine
  2. Build Site Search with Lyra and TypeScript - LogRocket - Comprehensive tutorial on implementing Lyra search
  3. Lyra GitHub Repository - Open source implementation and examples