npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sdflc/barcode-lookup

v1.0.0

Published

Resilient multi-source barcode and UPC product lookup library with intelligent caching and fallback strategies

Readme

@sdflc/barcode-lookup

npm version TypeScript License: MIT

Resilient multi-source barcode and UPC product lookup library with intelligent caching, fallback strategies, and comprehensive error handling. Works in both Node.js and browser environments.

🚀 Features

  • Multi-Source Support - Integrates with OpenFoodFacts, BarcodeLookup, UPCItemDB, and more
  • Intelligent Fallback - Automatically tries multiple sources until one succeeds
  • Enhanced Error Handling - Structured error information with retry logic and detailed diagnostics
  • Resilient Data Extraction - Handles API changes gracefully with flexible field mapping
  • Smart Caching - LRU cache with TTL to minimize API calls and improve performance
  • Configurable Retry Logic - Automatic retries with exponential backoff for transient failures
  • TypeScript First - Full type safety and excellent IDE support
  • Universal - Works in Node.js, browsers, and edge environments
  • Production Ready - Comprehensive monitoring, health checks, and debugging capabilities

📦 Installation

npm install @sdflc/barcode-lookup

🎯 Quick Start

import {
  BarcodeManager,
  OpenFoodFactsApi,
  UPCItemDBApi,
} from '@sdflc/barcode-lookup';

// Create manager and register sources
const manager = new BarcodeManager();

// Register free APIs (no API key required)
manager.register('openfoodfacts', new OpenFoodFactsApi());
manager.register('upcitemdb', new UPCItemDBApi());

// Look up a product
const product = await manager.lookup('3017620422003'); // Nutella

if (product) {
  console.log(`Product: ${product.name}`);
  console.log(`Brand: ${product.brand}`);
  console.log(`Source: ${product.sourceApi}`);
}

🔧 Enhanced Error Handling Example

import {
  BarcodeManager,
  OpenFoodFactsApi,
  UPCItemDBApi,
} from '@sdflc/barcode-lookup';

async function robustLookup() {
  const manager = new BarcodeManager();

  // Configure retry behavior
  manager.setRetryEnabled(true);
  manager.setMaxRetries(3);

  // Register sources
  manager.register('openfoodfacts', new OpenFoodFactsApi());
  manager.register('upcitemdb', new UPCItemDBApi());

  // Use enhanced lookup for detailed error information
  const result = await manager.lookupWithDetails('3017620422003');

  if (result.product) {
    console.log('Success:', {
      name: result.product.name,
      source: result.product.sourceApi,
      attempts: result.attempts,
      fromCache: result.fromCache,
    });
  } else if (result.error) {
    console.log('Failed:', {
      message: result.error.message,
      code: result.error.code,
      retryable: result.error.retryable,
      attempts: result.attempts,
    });

    // Implement custom retry logic if needed
    if (result.error.retryable && result.error.retryAfter) {
      console.log(`Can retry after ${result.error.retryAfter} seconds`);
    }
  }

  return result;
}

🎛️ API Reference

BarcodeManager

Main class for managing multiple barcode lookup sources with enhanced error handling.

Configuration Methods

  • register(name: string, source: BarcodeLookupApiSource, apiKey?: string) - Register a new API source
  • selectSource(source: string) - Select which source to use for lookups
  • setRetryEnabled(enabled: boolean) - Enable/disable automatic retry logic
  • setMaxRetries(count: number) - Set maximum retry attempts for failed requests
  • setCache(size: number) - Set cache size (0 to disable)

Lookup Methods

  • lookup(upc: string): Promise<LookupResult> - Simple lookup using current source
  • lookupWithDetails(upc: string): Promise<LookupResult> - Enhanced lookup with detailed error information
  • lookupFromAllSources(upc: string): Promise<LookupResult> - Try all sources until one succeeds

Monitoring & Management

  • clearCache() - Clear all cached items
  • getCacheStats() - Get cache statistics
  • getRegisteredSources(): string[] - Get list of registered source names
  • getCurrentSource(): string | null - Get currently selected source
  • healthCheck(sourceName?: string): Promise<HealthCheckResult> - Check health of specific source
  • healthCheckAll(): Promise<HealthCheckResult[]> - Check health of all sources
  • getUsageStats() - Get comprehensive usage and configuration statistics

Error Management

  • getSourceLastError(sourceName?: string): BarcodeLookupApiError | null - Get last error from specific source
  • clearSourceError(sourceName?: string): void - Clear error state for specific source

Enhanced Interfaces

LookupResult

interface LookupResult {
  product: ProductData | null;
  source?: string;
  fromCache: boolean;
  attempts: number;
  error?: BarcodeLookupApiError;
}

BarcodeLookupApiError

interface BarcodeLookupApiError {
  code: BarcodeLookupErrorCode;
  message: string;
  source: string;
  statusCode?: number;
  retryable: boolean;
  retryAfter?: number; // seconds to wait before retry
  timestamp: number;
  details?: {
    upc?: string;
    query?: string;
    originalError?: string;
    [key: string]: any;
  };
}

HealthCheckResult

interface HealthCheckResult {
  source: string;
  status: 'healthy' | 'degraded' | 'unhealthy';
  responseTime?: number;
  error?: string;
  lastError?: BarcodeLookupApiError;
}

API Sources

OpenFoodFactsApi

Free API for food products with rich nutrition data and enhanced error handling.

const api = new OpenFoodFactsApi();
manager.register('openfoodfacts', api);

// Check for errors after lookup
const result = await manager.lookupWithDetails('123456789');
if (result.error) {
  console.log(`OpenFoodFacts error: ${result.error.message}`);

  // Get structured error information
  const lastError = api.getLastError();
  if (lastError?.retryable) {
    console.log('This error can be retried');
  }
}

Features:

  • No API key required
  • Structured error handling with retry logic
  • Excellent for food products
  • Nutrition facts and ingredients
  • Multiple languages supported
  • Search functionality
  • Comprehensive error diagnostics

BarcodeLookupApi

Commercial API with comprehensive product database and full error handling.

const api = new BarcodeLookupApi('your-api-key');
manager.register('barcodelookup', api);

// Enhanced lookup with error handling
const result = await manager.lookupWithDetails('012000161155');

if (result.product) {
  // Access BarcodeLookup-specific features
  const stores = api.getStoreInfo(result.product);
  const reviews = api.getReviews(result.product);
  const specs = api.getSpecifications(result.product);

  stores.forEach(store => {
    console.log(`${store.name}: ${store.currency}${store.price}`);
  });
} else if (result.error) {
  console.log(`API Error: ${result.error.code} - ${result.error.message}`);

  if (result.error.code === 'RATE_LIMIT_EXCEEDED') {
    console.log(`Rate limited. Retry after ${result.error.retryAfter} seconds`);
  }
}

UPCItemDBApi

Free tier API with pricing focus and enhanced error handling.

const api = new UPCItemDBApi(); // Free tier
// or with premium key:
const api = new UPCItemDBApi('your-premium-key');
manager.register('upcitemdb', api);

// Handle API-specific errors
const result = await manager.lookupWithDetails('123456789');
if (result.error?.code === 'UNAUTHORIZED') {
  console.log('API key invalid or missing');
}

Features:

  • Free tier available with structured error handling
  • Premium tier with enhanced features
  • Multiple merchant offers
  • Price history tracking
  • Rate limit handling
  • Detailed error diagnostics

ProductData Interface

interface ProductData {
  upc: string; // Product UPC/EAN code
  name: string; // Product name (required)
  brand?: string; // Brand name
  category?: string; // Product category
  description?: string; // Product description
  imageUrl?: string; // Main product image
  ingredients?: string[]; // List of ingredients
  nutritionFacts?: object; // Nutrition information
  weight?: string; // Product weight
  dimensions?: string; // Product dimensions
  price?: number; // Current price
  currency?: string; // Price currency
  manufacturer?: string; // Manufacturer name
  countryOfOrigin?: string; // Country of origin
  sourceApi: string; // Which API provided the data
  rawData?: unknown; // Original API response
}

🔌 Creating Custom Sources

Create custom API sources with full error handling support:

import {
  BarcodeLookupApiSource,
  ProductData,
  BarcodeLookupApiError,
  createBarcodeLookupApiError,
  BarcodeLookupErrorCode,
  safeGet,
  safeString,
} from '@sdflc/barcode-lookup';

class MyCustomApi implements BarcodeLookupApiSource {
  name = 'MyCustomAPI';
  apiKey?: string;
  private lastError: BarcodeLookupApiError | null = null;

  constructor(apiKey?: string) {
    this.apiKey = apiKey;
  }

  // Required error handling methods
  getLastError(): BarcodeLookupApiError | null {
    return this.lastError;
  }

  clearLastError(): void {
    this.lastError = null;
  }

  isLastErrorRetryable(): boolean {
    return this.lastError ? this.lastError.retryable : false;
  }

  getRetryDelay(): number {
    return this.lastError?.retryAfter || 60;
  }

  async lookup(upc: string): Promise<ProductData | null> {
    this.clearLastError();

    try {
      const response = await fetch(`https://my-api.com/products/${upc}`, {
        headers: {
          Authorization: `Bearer ${this.apiKey}`,
          'Content-Type': 'application/json',
        },
      });

      if (!response.ok) {
        this.handleHttpError(response, upc);
        return null;
      }

      const data = await response.json();
      return this.transformToProductData(data, upc);
    } catch (error) {
      this.handleNetworkError(error, upc);
      return null;
    }
  }

  private handleHttpError(response: Response, upc: string): void {
    const statusCode = response.status;

    switch (statusCode) {
      case 401:
        this.lastError = createBarcodeLookupApiError(
          BarcodeLookupErrorCode.UNAUTHORIZED,
          'Invalid API key',
          this.name,
          { statusCode, retryable: false, details: { upc } }
        );
        break;

      case 429:
        const retryAfter = response.headers.get('Retry-After');
        this.lastError = createBarcodeLookupApiError(
          BarcodeLookupErrorCode.RATE_LIMIT_EXCEEDED,
          'Rate limit exceeded',
          this.name,
          {
            statusCode,
            retryable: true,
            retryAfter: retryAfter ? parseInt(retryAfter) : 60,
            details: { upc },
          }
        );
        break;

      default:
        this.lastError = createBarcodeLookupApiError(
          statusCode >= 500
            ? BarcodeLookupErrorCode.SERVER_ERROR
            : BarcodeLookupErrorCode.API_ERROR,
          `HTTP ${statusCode}`,
          this.name,
          { statusCode, retryable: statusCode >= 500, details: { upc } }
        );
    }
  }

  private handleNetworkError(error: any, upc: string): void {
    this.lastError = createBarcodeLookupApiError(
      BarcodeLookupErrorCode.NETWORK_ERROR,
      `Network error: ${error.message}`,
      this.name,
      { retryable: true, details: { upc, originalError: error.message } }
    );
  }

  private transformToProductData(
    apiData: any,
    upc: string
  ): ProductData | null {
    const name = safeString(apiData, ['product_name', 'title', 'name']);

    if (!name) {
      this.lastError = createBarcodeLookupApiError(
        BarcodeLookupErrorCode.DATA_TRANSFORMATION_ERROR,
        'No product name found',
        this.name,
        { retryable: false, details: { upc } }
      );
      return null;
    }

    return {
      upc,
      name,
      brand: safeString(apiData, ['brand', 'manufacturer']),
      sourceApi: this.name,
      rawData: apiData,
    };
  }
}

// Usage with full error handling
const customApi = new MyCustomApi('your-api-key');
manager.register('mycustom', customApi);

const result = await manager.lookupWithDetails('123456789');
if (result.error) {
  console.log(
    `Custom API Error: ${result.error.code} - ${result.error.message}`
  );
}

🛡️ Comprehensive Error Handling

The library provides structured error handling throughout:

// Simple error handling
try {
  const product = await manager.lookup('123456789');
  if (product) {
    console.log(product.name);
  } else {
    console.log('Product not found');
  }
} catch (error) {
  console.error('Lookup failed:', error.message);
}

// Detailed error handling
const result = await manager.lookupWithDetails('123456789');

if (result.product) {
  console.log('Success:', result.product.name);
} else if (result.error) {
  console.log('Failed with structured error:');
  console.log(`- Code: ${result.error.code}`);
  console.log(`- Message: ${result.error.message}`);
  console.log(`- Source: ${result.error.source}`);
  console.log(`- Retryable: ${result.error.retryable}`);
  console.log(`- Attempts: ${result.attempts}`);

  if (result.error.retryAfter) {
    console.log(`- Retry after: ${result.error.retryAfter} seconds`);
  }

  // Handle specific error types
  switch (result.error.code) {
    case 'RATE_LIMIT_EXCEEDED':
      console.log('Rate limited - implement exponential backoff');
      break;
    case 'INVALID_API_KEY':
      console.log('Check API key configuration');
      break;
    case 'NETWORK_ERROR':
      console.log('Network issue - check connectivity');
      break;
  }
}

⚡ Performance & Caching

Enhanced caching with monitoring capabilities:

// Configure cache and retry behavior
const manager = new BarcodeManager({
  cacheSize: 200,
  cacheTtl: 4 * 60 * 60 * 1000, // 4 hours
});

manager.setRetryEnabled(true);
manager.setMaxRetries(3);

// Monitor performance
const result1 = await manager.lookupWithDetails('123456789'); // API call
const result2 = await manager.lookupWithDetails('123456789'); // Cache hit

console.log('Performance comparison:');
console.log(
  `First lookup: ${result1.attempts} attempts, cache: ${result1.fromCache}`
);
console.log(
  `Second lookup: ${result2.attempts} attempts, cache: ${result2.fromCache}`
);

// Get comprehensive statistics
const stats = manager.getUsageStats();
console.log('Usage stats:', {
  sources: stats.totalSources,
  cache: `${stats.cacheStats.size}/${stats.cacheStats.maxSize}`,
  retryEnabled: stats.retryEnabled,
  maxRetries: stats.maxRetries,
});

🏥 Health Monitoring

Monitor API health and error states:

// Check health of all sources
const healthResults = await manager.healthCheckAll();

healthResults.forEach(result => {
  console.log(`${result.source}: ${result.status}`);

  if (result.responseTime) {
    console.log(`  Response time: ${result.responseTime}ms`);
  }

  if (result.lastError) {
    console.log(
      `  Last error: ${result.lastError.code} - ${result.lastError.message}`
    );
  }
});

// Monitor error states
const sourcesInfo = manager.getSourcesInfo();
sourcesInfo.forEach(source => {
  console.log(`${source.name}:`);
  console.log(
    `  Error handling: ${source.supportsErrorHandling ? 'Enhanced' : 'Basic'}`
  );
  console.log(`  Cached items: ${source.cachedItems}`);

  if (source.lastError) {
    console.log(`  Current error: ${source.lastError.message}`);
  }
});

🌐 Browser Support

Works in modern browsers with enhanced error handling:

<script type="module">
  import {
    BarcodeManager,
    OpenFoodFactsApi,
  } from 'https://unpkg.com/@sdflc/barcode-lookup/dist/index.mjs';

  const manager = new BarcodeManager();
  manager.register('openfoodfacts', new OpenFoodFactsApi());

  // Use enhanced lookup in browser
  const result = await manager.lookupWithDetails('3017620422003');

  if (result.product) {
    console.log('Product found:', result.product.name);
  } else if (result.error) {
    console.error('Error:', result.error.message);

    // Handle browser-specific network errors
    if (result.error.code === 'NETWORK_ERROR') {
      console.log('Check internet connection');
    }
  }
</script>

🔧 Advanced Configuration

Environment Setup with Error Handling

# API Keys
BARCODE_LOOKUP_API_KEY=your-api-key
UPCITEMDB_PREMIUM_KEY=your-premium-key

# Cache Configuration
BARCODE_LOOKUP_DEFAULT_CACHE_SIZE=200
BARCODE_LOOKUP_DEFAULT_CACHE_TTL=86400000

# Retry Configuration
BARCODE_LOOKUP_ENABLE_RETRIES=true
BARCODE_LOOKUP_MAX_RETRIES=3
import dotenv from 'dotenv';
dotenv.config();

const manager = new BarcodeManager({
  cacheSize: parseInt(process.env.BARCODE_LOOKUP_DEFAULT_CACHE_SIZE || '100'),
  cacheTtl: parseInt(
    process.env.BARCODE_LOOKUP_DEFAULT_CACHE_TTL || '86400000'
  ),
});

// Configure retry behavior from environment
manager.setRetryEnabled(process.env.BARCODE_LOOKUP_ENABLE_RETRIES === 'true');
manager.setMaxRetries(parseInt(process.env.BARCODE_LOOKUP_MAX_RETRIES || '2'));

Production Monitoring

// Production-ready error monitoring
async function productionLookup(upc: string) {
  const result = await manager.lookupWithDetails(upc);

  // Log metrics for monitoring
  console.log(
    JSON.stringify({
      timestamp: new Date().toISOString(),
      upc,
      success: !!result.product,
      source: result.source,
      attempts: result.attempts,
      fromCache: result.fromCache,
      error: result.error
        ? {
            code: result.error.code,
            message: result.error.message,
            retryable: result.error.retryable,
          }
        : null,
    })
  );

  return result;
}

// Health check endpoint for monitoring
app.get('/health/barcode-apis', async (req, res) => {
  const health = await manager.healthCheckAll();
  const overallHealth = health.every(h => h.status === 'healthy')
    ? 'healthy'
    : 'degraded';

  res.json({
    status: overallHealth,
    sources: health,
    timestamp: new Date().toISOString(),
  });
});

📋 Requirements

  • Node.js 16+ (for Node.js usage)
  • Modern browser with fetch support (for browser usage)
  • TypeScript 4.5+ (for TypeScript projects)

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🆘 Support

🙏 Acknowledgments


Made with ❤️ for developers who need reliable barcode lookups with comprehensive error handling