@sdflc/barcode-lookup
v1.0.0
Published
Resilient multi-source barcode and UPC product lookup library with intelligent caching and fallback strategies
Maintainers
Readme
@sdflc/barcode-lookup
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 sourceselectSource(source: string)- Select which source to use for lookupssetRetryEnabled(enabled: boolean)- Enable/disable automatic retry logicsetMaxRetries(count: number)- Set maximum retry attempts for failed requestssetCache(size: number)- Set cache size (0 to disable)
Lookup Methods
lookup(upc: string): Promise<LookupResult>- Simple lookup using current sourcelookupWithDetails(upc: string): Promise<LookupResult>- Enhanced lookup with detailed error informationlookupFromAllSources(upc: string): Promise<LookupResult>- Try all sources until one succeeds
Monitoring & Management
clearCache()- Clear all cached itemsgetCacheStats()- Get cache statisticsgetRegisteredSources(): string[]- Get list of registered source namesgetCurrentSource(): string | null- Get currently selected sourcehealthCheck(sourceName?: string): Promise<HealthCheckResult>- Check health of specific sourcehealthCheckAll(): Promise<HealthCheckResult[]>- Check health of all sourcesgetUsageStats()- Get comprehensive usage and configuration statistics
Error Management
getSourceLastError(sourceName?: string): BarcodeLookupApiError | null- Get last error from specific sourceclearSourceError(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=3import 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
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🆘 Support
🙏 Acknowledgments
- OpenFoodFacts - Amazing open food database
- BarcodeLookup.com - Comprehensive product database
- UPCitemdb.com - UPC database with pricing
Made with ❤️ for developers who need reliable barcode lookups with comprehensive error handling
