Code Examples

Practical examples showing how to use the DDG Web Search package in different scenarios.

Basic Usage

Simple example showing how to search the web and fetch web content.

basic-usage.ts
import { WebSearcher, WebContentFetcher } from "@lucid-spark/ddg-web-search";

async function main() {
  const searcher = new WebSearcher();
  const fetcher = new WebContentFetcher();

  try {
    // Search the web using DuckDuckGo with browser automation
    console.log("šŸ” Searching for TypeScript tutorials...");
    const results = await searcher.search("TypeScript tutorials");
    
    console.log(`āœ… Found ${results.length} results:`);
    results.forEach((result, index) => {
      console.log(`${index + 1}. ${result.title}`);
      console.log(`   URL: ${result.url}`);
      console.log(`   Snippet: ${result.snippet}`);
      if (result.icon) {
        console.log(`   Icon: ${result.icon}`);
      }
      console.log();
    });

    // Fetch content from the first result
    if (results.length > 0) {
      console.log("šŸ“„ Fetching content from first result...");
      const fetchResult = await fetcher.fetch(results[0].url);
      
      if (fetchResult.success && fetchResult.data?.content) {
        console.log(`āœ… Content fetched successfully!`);
        console.log(`šŸ“Š Content length: ${fetchResult.data.content.length} characters`);
        console.log(`šŸ“ Title: ${fetchResult.data.metadata?.title || 'N/A'}`);
        console.log(`šŸ“ Preview: ${fetchResult.data.content.substring(0, 200)}...`);
      } else {
        console.log(`āŒ Failed to fetch content: ${fetchResult.error}`);
      }
    }
  } catch (error) {
    console.error("āŒ Error:", error);
  } finally {
    // Important: cleanup browser resources
    await searcher.close();
  }
}

main();

Command Line Interface

Examples of using the built-in CLI for quick searches and content fetching.

Terminal Commands
# Install globally for CLI access
npm install -g @lucid-spark/ddg-web-search

# Search the web with browser automation
ddg-web-search search "Node.js best practices"

# Fetch content from a URL with intelligent parsing
ddg-web-search fetch https://nodejs.org/en/docs/

# Start interactive mode
ddg-web-search interactive

# Start MCP server (stdio transport)
ddg-web-search mcp

# Start MCP server (HTTP transport)
ddg-web-search mcp-http

# In interactive mode:
šŸ¦† ddg> search "React hooks tutorial"
šŸ¦† ddg> fetch https://reactjs.org/docs/hooks-intro.html
šŸ¦† ddg> mcp
šŸ¦† ddg> help
šŸ¦† ddg> exit

MCP Server Integration

Setting up the Model Context Protocol server for AI assistant integration.

mcp-setup.json
{
  "mcpServers": {
    "ddg-web-search": {
      "command": "@lucid-spark/ddg-web-search-mcp",
      "args": [],
      "env": {}
    }
  }
}
Terminal - MCP Server
# Start MCP server with stdio transport (default)
ddg-web-search-mcp

# Start with HTTP transport
node dist/mcp.js --transport http --port 3001

# Start with custom host and port
node dist/mcp.js --transport http --host 0.0.0.0 --port 8080

# Test HTTP transport with curl
curl http://localhost:3001/

# Available MCP tools:
# - search: Search the web for results
# - fetch_web_content: Fetch content from a web URL

Advanced Configuration

Advanced usage with custom rate limiting and error handling.

advanced-usage.ts
import { 
  WebSearcher, 
  WebContentFetcher, 
  RateLimiter,
  HttpClient 
} from "@lucid-spark/ddg-web-search";

async function advancedExample() {
  // Custom rate limiting: 2 requests per 3 seconds
  const customFetcher = new WebContentFetcher(2, 3000);
  const searcher = new WebSearcher();

  try {
    // Batch search multiple queries
    const queries = [
      "TypeScript best practices",
      "Node.js performance optimization",
      "React testing strategies"
    ];

    console.log("šŸ” Performing batch searches...");
    
    for (const query of queries) {
      try {
        console.log(`\nšŸ“ Searching: "${query}"`);
        const results = await searcher.search(query);
        
        console.log(`   āœ… Found ${results.length} results`);
        
        // Fetch content from first result if available
        if (results.length > 0) {
          const fetchResult = await customFetcher.fetch(results[0].url);
          
          if (fetchResult.success && fetchResult.data?.content) {
            console.log(`   šŸ“„ Content length: ${fetchResult.data.content.length} chars`);
            console.log(`   šŸ“ Title: ${fetchResult.data.metadata?.title || 'N/A'}`);
          } else {
            console.log(`   āŒ Fetch failed: ${fetchResult.error}`);
          }
        }
      } catch (error) {
        console.error(`   āŒ Error searching "${query}":`, error);
      }
    }
  } finally {
    // Essential cleanup
    await searcher.close();
  }
}

// Error handling example
async function errorHandlingExample() {
  const fetcher = new WebContentFetcher();
  
  const urls = [
    "https://httpbin.org/status/200",  // Should succeed
    "https://httpbin.org/status/404",  // Should fail
    "invalid-url",                     // Invalid URL
    "https://httpbin.org/delay/5"      // Might timeout
  ];
  
  for (const url of urls) {
    try {
      console.log(`\nšŸ”— Fetching: ${url}`);
      const result = await fetcher.fetch(url);
      
      if (result.success && result.data?.content) {
        console.log(`   āœ… Success: ${result.data.content.length} characters`);
      } else {
        console.log(`   āŒ Failed: ${result.error}`);
      }
    } catch (error) {
      console.log(`   šŸ’„ Exception: ${error}`);
    }
  }
}

// Run examples
advancedExample().then(() => {
  console.log("\n" + "=".repeat(50));
  return errorHandlingExample();
}).catch(console.error);

Express.js API Integration

Building a REST API with Express.js using the DDG Web Search package.

express-api.js
const express = require('express');
const { WebSearcher, WebContentFetcher } = require('@lucid-spark/ddg-web-search');

const app = express();
const port = 3000;

// Initialize services
const searcher = new WebSearcher();
const fetcher = new WebContentFetcher();

app.use(express.json());

// Search endpoint
app.get('/api/search', async (req, res) => {
  try {
    const { q } = req.query;
    
    if (!q) {
      return res.status(400).json({ 
        error: 'Query parameter "q" is required' 
      });
    }

    const results = await searcher.search(q);
    
    res.json({
      query: q,
      results: results,
      count: results.length,
      timestamp: new Date().toISOString()
    });
  } catch (error) {
    res.status(500).json({ 
      error: 'Search failed', 
      message: error.message 
    });
  }
});

// Fetch content endpoint
app.post('/api/fetch', async (req, res) => {
  try {
    const { url } = req.body;
    
    if (!url) {
      return res.status(400).json({ 
        error: 'URL is required in request body' 
      });
    }

    const result = await fetcher.fetch(url);
    
    if (result.success && result.data?.content) {
      res.json({
        url: url,
        content: result.data.content,
        length: result.data.content.length,
        metadata: result.data.metadata,
        timestamp: new Date().toISOString()
      });
    } else {
      res.status(400).json({
        url: url,
        error: result.error,
        timestamp: new Date().toISOString()
      });
    }
  } catch (error) {
    res.status(500).json({ 
      error: 'Fetch failed', 
      message: error.message 
    });
  }
});

// Health check
app.get('/health', (req, res) => {
  res.json({ 
    status: 'healthy', 
    timestamp: new Date().toISOString() 
  });
});

app.listen(port, () => {
  console.log(`šŸš€ DDG Web Search API listening at http://localhost:${port}`);
  console.log(`šŸ“‹ Endpoints:`);
  console.log(`   GET  /api/search?q=`);
  console.log(`   POST /api/fetch (body: {"url": "..."})`);
  console.log(`   GET  /health`);
});

TypeScript Types & Interfaces

Working with TypeScript types for better development experience.

types-example.ts
import { 
  SearchResult, 
  FetchResult, 
  WebContent,
  WebSearcher, 
  WebContentFetcher 
} from "@lucid-spark/ddg-web-search";

// Type-safe search function
async function searchWithTypes(query: string): Promise {
  const searcher = new WebSearcher();
  return await searcher.search(query);
}

// Type-safe fetch function
async function fetchWithTypes(url: string): Promise {
  const fetcher = new WebContentFetcher();
  return await fetcher.fetch(url);
}

// Custom interface extending the base types
interface SearchWithMetadata extends SearchResult {
  searchTimestamp: Date;
  relevanceScore?: number;
}

// Custom service class with proper typing
class SearchService {
  private searcher: WebSearcher;
  private fetcher: WebContentFetcher;

  constructor() {
    this.searcher = new WebSearcher();
    this.fetcher = new WebContentFetcher(1, 2000); // 1 req per 2 seconds
  }

  async searchWithMetadata(query: string): Promise {
    const results = await this.searcher.search(query);
    
    return results.map((result, index) => ({
      ...result,
      searchTimestamp: new Date(),
      relevanceScore: Math.max(0, 1 - (index * 0.1)) // Simple relevance scoring
    }));
  }

  async fetchContent(url: string): Promise {
    const result = await this.fetcher.fetch(url);
    
    if (result.success && result.data?.content) {
      return {
        content: result.data.content,
        metadata: {
          title: this.extractTitle(result.data.content),
          description: this.extractDescription(result.data.content),
          url: url
        }
      };
    }
    
    return null;
  }

  private extractTitle(content: string): string {
    const titleMatch = content.match(/]*>([^<]+)<\/title>/i);
    return titleMatch ? titleMatch[1].trim() : 'No title found';
  }

  private extractDescription(content: string): string {
    const descMatch = content.match(/]*name="description"[^>]*content="([^"]+)"/i);
    return descMatch ? descMatch[1].trim() : content.substring(0, 160) + '...';
  }
}

// Usage example
async function main() {
  const service = new SearchService();
  
  const results: SearchWithMetadata[] = await service.searchWithMetadata("TypeScript");
  
  for (const result of results.slice(0, 3)) {
    console.log(`šŸ“„ ${result.title}`);
    console.log(`šŸ”— ${result.url}`);
    console.log(`⭐ Relevance: ${result.relevanceScore?.toFixed(2)}`);
    console.log(`šŸ• Searched: ${result.searchTimestamp.toISOString()}`);
    
    const content = await service.fetchContent(result.url);
    if (content) {
      console.log(`šŸ“ ${content.metadata?.description}`);
    }
    console.log('---');
  }
}

main().catch(console.error);

Testing

Unit testing examples using Jest with mocked dependencies.

search.test.ts
import { WebSearcher } from '@lucid-spark/ddg-web-search';
// Note: This is a mock example - the actual WebSearcher uses Puppeteer for browser automation

describe('WebSearcher', () => {
  let searcher: WebSearcher;

  beforeEach(() => {
    searcher = new WebSearcher();
  });

  afterEach(async () => {
    // Important: cleanup browser resources
    await searcher.close();
  });

  it('should return search results for valid query', async () => {
    const results = await searcher.search('TypeScript');

    expect(Array.isArray(results)).toBe(true);
    expect(results.length).toBeGreaterThan(0);
    
    if (results.length > 0) {
      expect(results[0]).toHaveProperty('title');
      expect(results[0]).toHaveProperty('url');
      expect(results[0]).toHaveProperty('snippet');
      expect(typeof results[0].title).toBe('string');
      expect(typeof results[0].url).toBe('string');
      expect(typeof results[0].snippet).toBe('string');
    }
  });

  it('should return empty array for empty query', async () => {
    const results = await searcher.search('');
    
    expect(results).toEqual([]);
  });

  it('should handle search errors gracefully', async () => {
    // Test with very long query that might cause issues
    const veryLongQuery = 'a'.repeat(1000);
    const results = await searcher.search(veryLongQuery);
    
    expect(Array.isArray(results)).toBe(true);
  });
});