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.
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.
1mkdir lyra-search-demo2cd lyra-search-demo3npm init -y4npm install lyra typescript @types/node --save-dev5npx tsc --initTypeScript 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.
| Type | Description | Use Case |
|---|---|---|
| string | Text field with configurable tokenizer | Titles, content, descriptions |
| number | Numeric values for range queries | Prices, dates as numbers |
| boolean | Binary true/false values | Flags, status indicators |
| array | Multi-value fields | Tags, categories, keywords |
| datetime | Date/time values with temporal filtering | Publication 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.
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
limitparameter to prevent over-fetching and reduce rendering overhead - Select properties: Use the
propertiesoption 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.
| Feature | Lyra | Lunr.js | FlexSearch |
|---|---|---|---|
| TypeScript Support | Native | Requires types | Partial |
| Bundle Size | ~10KB | ~25KB | ~15KB |
| Typo Tolerance | Built-in | Plugin | Built-in |
| Edge Ready | Yes | No | Limited |
| Last Update | Active | Maintenance | Active |
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
Sources
- Lyra Official Documentation - Official documentation for the Lyra search engine
- Build Site Search with Lyra and TypeScript - LogRocket - Comprehensive tutorial on implementing Lyra search
- Lyra GitHub Repository - Open source implementation and examples