You’re building a lead generation tool when suddenly you realize you need the website URLs for 500 companies—and you need them now. Manual research? That’s 8+ hours of tedious copy-pasting. But with JavaScript and the right API, you can transform those company names into verified website URLs in under 2 minutes.
JavaScript developers have a unique advantage when it comes to Company Name to Domain APIs. Whether you’re building client-side applications, Node.js backends, or serverless functions, JavaScript’s asynchronous nature makes it perfect for handling bulk domain lookups efficiently. The challenge isn’t just making API calls—it’s doing it right.
Why JavaScript Developers Need Domain Finding APIs
Modern web applications demand real-time data enrichment. When users input company names into your CRM, sales tool, or analytics dashboard, they expect instant results. JavaScript’s event-driven architecture makes it ideal for handling these dynamic lookup requirements without blocking the user interface.
The complexity comes from scale. Finding the best Company Name to Domain finder isn’t just about accuracy—it’s about handling rate limits, managing async operations, and providing seamless user experiences. A quality API like CUFinder’s Company Name to Domain service processes requests in milliseconds while maintaining 98% accuracy across 85M+ companies.
JavaScript’s strength lies in its flexibility. You can implement domain finding in React components for instant user feedback, Express.js middleware for server-side enrichment, or AWS Lambda functions for serverless batch processing. The key is choosing the right implementation pattern for your specific use case.

Getting Started: Basic JavaScript Implementation
Let’s start with a fundamental implementation that demonstrates core concepts. This approach works in both browser and Node.js environments, giving you maximum flexibility for finding company websites based on names.
class CompanyDomainFinder {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.cufinder.io/v2/cuf';
this.headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'x-api-key': this.apiKey
};
}
async findDomain(companyName) {
if (!companyName || typeof companyName !== 'string') {
throw new Error('Company name must be a non-empty string');
}
const formData = new URLSearchParams();
formData.append('company_name', companyName.trim());
try {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: this.headers,
body: formData
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
// Validate response structure
if (result.status !== 1 || !result.data) {
throw new Error('Invalid API response structure');
}
return {
success: true,
data: {
companyName: companyName,
domain: result.data.domain,
confidence: result.data.confidence_level,
creditsRemaining: result.data.credit_count
}
};
} catch (error) {
return {
success: false,
error: error.message,
companyName: companyName
};
}
}
async findMultipleDomains(companyNames, options = {}) {
const {
concurrency = 5,
delayMs = 100,
retryAttempts = 2
} = options;
// Remove duplicates while preserving order
const uniqueCompanies = [...new Set(companyNames.filter(Boolean))];
const results = [];
// Process in batches to respect rate limits
for (let i = 0; i < uniqueCompanies.length; i += concurrency) {
const batch = uniqueCompanies.slice(i, i + concurrency);
const batchPromises = batch.map(async (company) => {
return this.findDomainWithRetry(company, retryAttempts);
});
const batchResults = await Promise.allSettled(batchPromises);
// Process batch results
batchResults.forEach((result, index) => {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
results.push({
success: false,
error: result.reason.message,
companyName: batch[index]
});
}
});
// Add delay between batches to respect rate limits
if (i + concurrency < uniqueCompanies.length) {
await this.delay(delayMs);
}
}
return results;
}
async findDomainWithRetry(companyName, maxRetries = 2) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const result = await this.findDomain(companyName);
if (result.success) {
return result;
}
lastError = new Error(result.error);
// Don't retry on certain errors
if (result.error.includes('Invalid company name')) {
break;
}
// Exponential backoff for retries
if (attempt < maxRetries) {
await this.delay(1000 * Math.pow(2, attempt));
}
} catch (error) {
lastError = error;
// Exponential backoff for retries
if (attempt < maxRetries) {
await this.delay(1000 * Math.pow(2, attempt));
}
}
}
throw lastError;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage example
const finder = new CompanyDomainFinder('your-api-key');
// Single company lookup
finder.findDomain('Apple Inc')
.then(result => {
if (result.success) {
console.log(`Found domain: ${result.data.domain} (${result.data.confidence}% confidence)`);
} else {
console.error(`Error: ${result.error}`);
}
});
// Multiple companies
const companies = ['Microsoft Corporation', 'Google LLC', 'Amazon Inc'];
finder.findMultipleDomains(companies)
.then(results => {
results.forEach(result => {
if (result.success) {
console.log(`${result.data.companyName}: ${result.data.domain}`);
} else {
console.log(`${result.companyName}: Error - ${result.error}`);
}
});
});
This implementation provides a solid foundation with error handling, retry logic, and rate limiting. The async/await pattern makes it easy to integrate into modern JavaScript applications while maintaining readable code.
React Integration: Real-Time Domain Lookup Component
React applications often need real-time domain lookups as users input company data. Here’s a complete implementation that demonstrates how to get website URLs from company names in bulk while providing excellent user experience:
import React, { useState, useCallback, useEffect } from 'react';
import { debounce } from 'lodash';
const CompanyDomainLookup = ({ apiKey, onDomainFound }) => {
const [companyName, setCompanyName] = useState('');
const [domainResult, setDomainResult] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const finder = new CompanyDomainFinder(apiKey);
// Debounced domain lookup to avoid excessive API calls
const debouncedLookup = useCallback(
debounce(async (company) => {
if (!company || company.length < 3) {
setDomainResult(null);
return;
}
setIsLoading(true);
setError(null);
try {
const result = await finder.findDomain(company);
if (result.success) {
setDomainResult(result.data);
// Callback to parent component
if (onDomainFound) {
onDomainFound(result.data);
}
} else {
setError(result.error);
setDomainResult(null);
}
} catch (err) {
setError(err.message);
setDomainResult(null);
} finally {
setIsLoading(false);
}
}, 500),
[finder, onDomainFound]
);
useEffect(() => {
debouncedLookup(companyName);
// Cleanup debounced function
return () => {
debouncedLookup.cancel();
};
}, [companyName, debouncedLookup]);
const handleInputChange = (e) => {
setCompanyName(e.target.value);
};
const getConfidenceColor = (confidence) => {
if (confidence >= 90) return 'text-green-600';
if (confidence >= 70) return 'text-yellow-600';
return 'text-red-600';
};
return (
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
<div className="mb-4">
<label htmlFor="company-input" className="block text-sm font-medium text-gray-700 mb-2">
Company Name
</label>
<input
id="company-input"
type="text"
value={companyName}
onChange={handleInputChange}
placeholder="Enter company name..."
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
{isLoading && (
<div className="flex items-center justify-center py-4">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
<span className="ml-2 text-gray-600">Finding domain...</span>
</div>
)}
{error && (
<div className="bg-red-50 border border-red-200 rounded-md p-3 mb-4">
<p className="text-red-700 text-sm">{error}</p>
</div>
)}
{domainResult && !isLoading && (
<div className="bg-green-50 border border-green-200 rounded-md p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-700">Website Found:</span>
<span className={`text-sm font-medium ${getConfidenceColor(domainResult.confidence)}`}>
{domainResult.confidence}% confidence
</span>
</div>
<a
href={domainResult.domain}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 font-medium break-all"
>
{domainResult.domain}
</a>
<p className="text-xs text-gray-500 mt-2">
Credits remaining: {domainResult.creditsRemaining}
</p>
</div>
)}
</div>
);
};
// Bulk lookup component for handling multiple companies
const BulkCompanyLookup = ({ apiKey }) => {
const [companies, setCompanies] = useState('');
const [results, setResults] = useState([]);
const [isProcessing, setIsProcessing] = useState(false);
const [progress, setProgress] = useState(0);
const finder = new CompanyDomainFinder(apiKey);
const handleBulkLookup = async () => {
const companyList = companies
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
if (companyList.length === 0) {
alert('Please enter at least one company name');
return;
}
setIsProcessing(true);
setProgress(0);
setResults([]);
try {
// Process with progress tracking
const batchSize = 5;
const allResults = [];
for (let i = 0; i < companyList.length; i += batchSize) {
const batch = companyList.slice(i, i + batchSize);
const batchResults = await finder.findMultipleDomains(batch);
allResults.push(...batchResults);
setResults([...allResults]);
// Update progress
const progressPercent = Math.min(100, ((i + batchSize) / companyList.length) * 100);
setProgress(progressPercent);
}
} catch (error) {
console.error('Bulk lookup failed:', error);
} finally {
setIsProcessing(false);
setProgress(100);
}
};
const exportResults = () => {
const csvContent = [
['Company Name', 'Website', 'Confidence', 'Status'],
...results.map(result => [
result.companyName || result.data?.companyName,
result.success ? result.data.domain : 'Not found',
result.success ? `${result.data.confidence}%` : 'N/A',
result.success ? 'Success' : 'Error'
])
].map(row => row.join(',')).join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'company-domains.csv';
a.click();
URL.revokeObjectURL(url);
};
return (
<div className="max-w-4xl mx-auto p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-6">Bulk Company Domain Lookup</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div>
<label htmlFor="companies-input" className="block text-sm font-medium text-gray-700 mb-2">
Company Names (one per line)
</label>
<textarea
id="companies-input"
value={companies}
onChange={(e) => setCompanies(e.target.value)}
placeholder="Apple Inc Microsoft Corporation Google LLC"
rows={10}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<div className="mt-4 flex gap-3">
<button
onClick={handleBulkLookup}
disabled={isProcessing}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
>
{isProcessing ? 'Processing...' : 'Find Domains'}
</button>
{results.length > 0 && (
<button
onClick={exportResults}
className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700"
>
Export CSV
</button>
)}
</div>
{isProcessing && (
<div className="mt-4">
<div className="bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${progress}%` }}
></div>
</div>
<p className="text-sm text-gray-600 mt-1">{Math.round(progress)}% complete</p>
</div>
)}
</div>
<div>
<h3 className="text-lg font-medium text-gray-900 mb-4">Results</h3>
<div className="max-h-96 overflow-y-auto border border-gray-300 rounded-md">
{results.map((result, index) => (
<div key={index} className="p-3 border-b border-gray-200 last:border-b-0">
<div className="flex justify-between items-start">
<div className="flex-1">
<p className="font-medium text-gray-900">
{result.companyName || result.data?.companyName}
</p>
{result.success ? (
<div>
<a
href={result.data.domain}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 text-sm break-all"
>
{result.data.domain}
</a>
<p className="text-xs text-gray-500">
{result.data.confidence}% confidence
</p>
</div>
) : (
<p className="text-red-600 text-sm">{result.error}</p>
)}
</div>
<span className={`px-2 py-1 text-xs rounded-full ${
result.success ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}>
{result.success ? 'Found' : 'Error'}
</span>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export { CompanyDomainLookup, BulkCompanyLookup };
These React components provide production-ready UI for both single and bulk domain lookups, complete with progress tracking, error handling, and CSV export functionality.
Node.js Server Implementation
For server-side applications, you need robust implementations that handle high concurrency, caching, and error recovery. Here’s a complete Express.js middleware solution:
const express = require('express');
const Redis = require('redis');
const rateLimit = require('express-rate-limit');
const { body, validationResult, query } = require('express-validator');
class ServerDomainFinder {
constructor(apiKey, redisConfig = {}) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.cufinder.io/v2/cuf';
// Initialize Redis for caching
this.redis = Redis.createClient(redisConfig);
this.redis.on('error', (err) => console.error('Redis Client Error', err));
this.redis.connect();
// Cache settings
this.cacheEnabled = true;
this.cacheTTL = 30 * 24 * 60 * 60; // 30 days
// Request queue for rate limiting
this.requestQueue = [];
this.isProcessingQueue = false;
this.maxConcurrentRequests = 5;
this.requestDelay = 100; // ms between requests
}
async findDomain(companyName) {
// Check cache first
if (this.cacheEnabled) {
const cacheKey = `domain:${companyName.toLowerCase().trim()}`;
try {
const cached = await this.redis.get(cacheKey);
if (cached) {
return { ...JSON.parse(cached), fromCache: true };
}
} catch (error) {
console.warn('Cache read error:', error);
}
}
// Make API request
const result = await this.makeApiRequest(companyName);
// Cache successful results
if (this.cacheEnabled && result.success && result.data.confidence >= 70) {
const cacheKey = `domain:${companyName.toLowerCase().trim()}`;
try {
await this.redis.setEx(cacheKey, this.cacheTTL, JSON.stringify(result));
} catch (error) {
console.warn('Cache write error:', error);
}
}
return result;
}
async makeApiRequest(companyName) {
const fetch = (await import('node-fetch')).default;
const { URLSearchParams } = require('url');
const formData = new URLSearchParams();
formData.append('company_name', companyName);
try {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'x-api-key': this.apiKey
},
body: formData
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (result.status !== 1 || !result.data) {
throw new Error('Invalid API response');
}
return {
success: true,
data: {
companyName: companyName,
domain: result.data.domain,
confidence: result.data.confidence_level,
creditsRemaining: result.data.credit_count
},
fromCache: false
};
} catch (error) {
return {
success: false,
error: error.message,
companyName: companyName,
fromCache: false
};
}
}
async findMultipleDomainsQueued(companyNames) {
return new Promise((resolve) => {
const requestId = Date.now() + Math.random();
this.requestQueue.push({
id: requestId,
companies: companyNames,
resolve: resolve,
timestamp: Date.now()
});
this.processQueue();
});
}
async processQueue() {
if (this.isProcessingQueue || this.requestQueue.length === 0) {
return;
}
this.isProcessingQueue = true;
while (this.requestQueue.length > 0) {
const request = this.requestQueue.shift();
const results = [];
// Process companies in batches
for (let i = 0; i < request.companies.length; i += this.maxConcurrentRequests) {
const batch = request.companies.slice(i, i + this.maxConcurrentRequests);
const batchPromises = batch.map(company => this.findDomain(company));
const batchResults = await Promise.allSettled(batchPromises);
batchResults.forEach((result, index) => {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
results.push({
success: false,
error: result.reason.message,
companyName: batch[index],
fromCache: false
});
}
});
// Rate limiting delay
if (i + this.maxConcurrentRequests < request.companies.length) {
await new Promise(resolve => setTimeout(resolve, this.requestDelay));
}
}
request.resolve(results);
}
this.isProcessingQueue = false;
}
// Express.js middleware
createMiddleware() {
return {
// Single domain lookup endpoint
singleLookup: [
body('companyName')
.trim()
.isLength({ min: 1, max: 200 })
.withMessage('Company name must be between 1 and 200 characters'),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
try {
const result = await this.findDomain(req.body.companyName);
res.json(result);
} catch (error) {
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
}
],
// Bulk lookup endpoint
bulkLookup: [
body('companies')
.isArray({ min: 1, max: 100 })
.withMessage('Companies must be an array with 1-100 items'),
body('companies.*')
.trim()
.isLength({ min: 1, max: 200 })
.withMessage('Each company name must be between 1 and 200 characters'),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
try {
// Remove duplicates
const uniqueCompanies = [...new Set(req.body.companies)];
const results = await this.findMultipleDomainsQueued(uniqueCompanies);
res.json({
success: true,
results: results,
summary: {
total: results.length,
successful: results.filter(r => r.success).length,
fromCache: results.filter(r => r.fromCache).length
}
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
}
],
// Health check endpoint
healthCheck: async (req, res) => {
try {
// Test Redis connection
await this.redis.ping();
// Test API with a simple request
const testResult = await this.findDomain('Test Company');
res.json({
status: 'healthy',
redis: 'connected',
api: 'accessible',
queueLength: this.requestQueue.length,
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message
});
}
}
};
}
}
// Express app setup
const app = express();
app.use(express.json());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 1000, // limit each IP to 1000 requests per windowMs
message: 'Too many requests from this IP'
});
app.use('/api/', limiter);
// Initialize domain finder
const domainFinder = new ServerDomainFinder(process.env.CUFINDER_API_KEY, {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
});
const middleware = domainFinder.createMiddleware();
// API routes
app.post('/api/domain/single', middleware.singleLookup);
app.post('/api/domain/bulk', middleware.bulkLookup);
app.get('/api/health', middleware.healthCheck);
// Error handling middleware
app.use((error, req, res, next) => {
console.error('Express error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Domain finder API server running on port ${PORT}`);
});
module.exports = { ServerDomainFinder };
This Node.js implementation provides enterprise-grade features including Redis caching, request queuing, rate limiting, and comprehensive error handling—perfect for production environments.
Serverless Implementation with AWS Lambda
Serverless functions are ideal for domain finding tasks that need to scale automatically. Here’s a complete AWS Lambda implementation that leverages CUFinder’s Company Name to Domain API:
const AWS = require('aws-sdk');
// Lambda function for single domain lookup
exports.singleDomainLookup = async (event) => {
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Content-Type': 'application/json'
};
// Handle preflight requests
if (event.httpMethod === 'OPTIONS') {
return {
statusCode: 200,
headers,
body: ''
};
}
try {
const body = JSON.parse(event.body);
const { companyName } = body;
if (!companyName || typeof companyName !== 'string') {
return {
statusCode: 400,
headers,
body: JSON.stringify({
success: false,
error: 'Company name is required'
})
};
}
const result = await findDomainWithCache(companyName);
return {
statusCode: 200,
headers,
body: JSON.stringify(result)
};
} catch (error) {
console.error('Lambda error:', error);
return {
statusCode: 500,
headers,
body: JSON.stringify({
success: false,
error: 'Internal server error'
})
};
}
};
// Lambda function for bulk domain lookup
exports.bulkDomainLookup = async (event) => {
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Content-Type': 'application/json'
};
if (event.httpMethod === 'OPTIONS') {
return { statusCode: 200, headers, body: '' };
}
try {
const body = JSON.parse(event.body);
const { companies, options = {} } = body;
if (!Array.isArray(companies) || companies.length === 0) {
return {
statusCode: 400,
headers,
body: JSON.stringify({
success: false,
error: 'Companies array is required'
})
};
}
if (companies.length > 50) {
return {
statusCode: 400,
headers,
body: JSON.stringify({
success: false,
error: 'Maximum 50 companies per request'
})
};
}
const results = await processBulkLookup(companies, options);
return {
statusCode: 200,
headers,
body: JSON.stringify({
success: true,
results,
summary: {
total: results.length,
successful: results.filter(r => r.success).length,
fromCache: results.filter(r => r.fromCache).length
}
})
};
} catch (error) {
console.error('Bulk lookup error:', error);
return {
statusCode: 500,
headers,
body: JSON.stringify({
success: false,
error: 'Internal server error'
})
};
}
};
// Scheduled Lambda for cache warming
exports.warmCache = async (event) => {
try {
// List of popular companies to keep in cache
const popularCompanies = [
'Apple Inc', 'Microsoft Corporation', 'Amazon Inc', 'Google LLC',
'Meta Platforms', 'Tesla Inc', 'Netflix Inc', 'Salesforce Inc',
'Oracle Corporation', 'Adobe Inc', 'Zoom Video Communications',
'Slack Technologies', 'Shopify Inc', 'Spotify Technology'
];
console.log(`Warming cache for ${popularCompanies.length} companies`);
const results = await processBulkLookup(popularCompanies, { useCache: true });
const successful = results.filter(r => r.success).length;
console.log(`Cache warming completed: ${successful}/${popularCompanies.length} successful`);
return {
statusCode: 200,
body: JSON.stringify({
message: 'Cache warming completed',
processed: popularCompanies.length,
successful: successful
})
};
} catch (error) {
console.error('Cache warming error:', error);
throw error;
}
};
// Helper functions
async function findDomainWithCache(companyName) {
const cacheKey = `domain:${companyName.toLowerCase().trim()}`;
// Try to get from DynamoDB cache
try {
const cached = await getCachedResult(cacheKey);
if (cached) {
return { ...cached, fromCache: true };
}
} catch (error) {
console.warn('Cache read error:', error);
}
// Make API request
const result = await makeApiRequest(companyName);
// Cache successful results
if (result.success && result.data.confidence >= 70) {
try {
await setCachedResult(cacheKey, result);
} catch (error) {
console.warn('Cache write error:', error);
}
}
return { ...result, fromCache: false };
}
async function makeApiRequest(companyName) {
const fetch = (await import('node-fetch')).default;
const formData = new URLSearchParams();
formData.append('company_name', companyName);
try {
const response = await fetch('https://api.cufinder.io/v2/cuf', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'x-api-key': process.env.CUFINDER_API_KEY
},
body: formData,
timeout: 10000 // 10 second timeout
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (result.status !== 1 || !result.data) {
throw new Error('Invalid API response');
}
return {
success: true,
data: {
companyName: companyName,
domain: result.data.domain,
confidence: result.data.confidence_level,
creditsRemaining: result.data.credit_count
}
};
} catch (error) {
return {
success: false,
error: error.message,
companyName: companyName
};
}
}
async function processBulkLookup(companies, options = {}) {
const { concurrency = 5, useCache = true } = options;
const uniqueCompanies = [...new Set(companies.filter(Boolean))];
const results = [];
// Process in batches
for (let i = 0; i < uniqueCompanies.length; i += concurrency) {
const batch = uniqueCompanies.slice(i, i + concurrency);
const batchPromises = batch.map(async (company) => {
if (useCache) {
return await findDomainWithCache(company);
} else {
const result = await makeApiRequest(company);
return { ...result, fromCache: false };
}
});
const batchResults = await Promise.allSettled(batchPromises);
batchResults.forEach((result, index) => {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
results.push({
success: false,
error: result.reason.message,
companyName: batch[index],
fromCache: false
});
}
});
// Small delay between batches
if (i + concurrency < uniqueCompanies.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
return results;
}
// DynamoDB cache functions
const dynamodb = new AWS.DynamoDB.DocumentClient();
const CACHE_TABLE = process.env.CACHE_TABLE_NAME || 'domain-lookup-cache';
const CACHE_TTL_DAYS = 30;
async function getCachedResult(key) {
try {
const result = await dynamodb.get({
TableName: CACHE_TABLE,
Key: { cacheKey: key }
}).promise();
if (result.Item) {
// Check if cache entry is still valid
const now = Math.floor(Date.now() / 1000);
if (result.Item.ttl > now) {
return result.Item.data;
} else {
// Clean up expired entry
await dynamodb.delete({
TableName: CACHE_TABLE,
Key: { cacheKey: key }
}).promise();
}
}
return null;
} catch (error) {
console.error('DynamoDB get error:', error);
return null;
}
}
async function setCachedResult(key, data) {
try {
const ttl = Math.floor(Date.now() / 1000) + (CACHE_TTL_DAYS * 24 * 60 * 60);
await dynamodb.put({
TableName: CACHE_TABLE,
Item: {
cacheKey: key,
data: data,
ttl: ttl,
createdAt: new Date().toISOString()
}
}).promise();
} catch (error) {
console.error('DynamoDB put error:', error);
}
}
This serverless implementation includes automatic scaling, DynamoDB caching, and scheduled cache warming—perfect for applications with variable workloads.
Advanced Browser Implementation with Web Workers
For computationally intensive browser applications, Web Workers allow you to perform domain lookups without blocking the main thread. This is especially useful when implementing Python-like bulk processing in the browser:
// main.js - Main thread implementation
class BrowserDomainFinder {
constructor(apiKey) {
this.apiKey = apiKey;
this.worker = null;
this.requestId = 0;
this.pendingRequests = new Map();
this.initializeWorker();
}
initializeWorker() {
// Create Web Worker from inline script
const workerScript = `
let apiKey = '';
let requestQueue = [];
let isProcessing = false;
self.onmessage = async function(e) {
const { type, data, requestId } = e.data;
switch (type) {
case 'init':
apiKey = data.apiKey;
break;
case 'findDomain':
await processSingleRequest(data.companyName, requestId);
break;
case 'findMultipleDomains':
await processBulkRequest(data.companies, data.options, requestId);
break;
}
};
async function processSingleRequest(companyName, requestId) {
try {
const result = await findDomain(companyName);
self.postMessage({
type: 'result',
requestId: requestId,
success: true,
data: result
});
} catch (error) {
self.postMessage({
type: 'result',
requestId: requestId,
success: false,
error: error.message
});
}
}
async function processBulkRequest(companies, options, requestId) {
const { concurrency = 3, delayMs = 200 } = options || {};
const results = [];
try {
for (let i = 0; i < companies.length; i += concurrency) {
const batch = companies.slice(i, i + concurrency);
const batchPromises = batch.map(company => findDomain(company));
const batchResults = await Promise.allSettled(batchPromises);
batchResults.forEach((result, index) => {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
results.push({
success: false,
error: result.reason.message,
companyName: batch[index]
});
}
});
// Progress update
const progress = Math.min(100, ((i + concurrency) / companies.length) * 100);
self.postMessage({
type: 'progress',
requestId: requestId,
progress: progress,
completed: results.length,
total: companies.length
});
// Delay between batches
if (i + concurrency < companies.length) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
self.postMessage({
type: 'result',
requestId: requestId,
success: true,
data: results
});
} catch (error) {
self.postMessage({
type: 'result',
requestId: requestId,
success: false,
error: error.message
});
}
}
async function findDomain(companyName) {
const formData = new URLSearchParams();
formData.append('company_name', companyName);
const response = await fetch('https://api.cufinder.io/v2/cuf', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'x-api-key': apiKey
},
body: formData
});
if (!response.ok) {
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
}
const result = await response.json();
if (result.status !== 1 || !result.data) {
throw new Error('Invalid API response');
}
return {
success: true,
data: {
companyName: companyName,
domain: result.data.domain,
confidence: result.data.confidence_level,
creditsRemaining: result.data.credit_count
}
};
}
`;
const blob = new Blob([workerScript], { type: 'application/javascript' });
this.worker = new Worker(URL.createObjectURL(blob));
// Initialize worker with API key
this.worker.postMessage({
type: 'init',
data: { apiKey: this.apiKey }
});
// Handle worker messages
this.worker.onmessage = (e) => {
const { type, requestId, success, data, error, progress, completed, total } = e.data;
const request = this.pendingRequests.get(requestId);
if (!request) return;
switch (type) {
case 'result':
if (success) {
request.resolve(data);
} else {
request.reject(new Error(error));
}
this.pendingRequests.delete(requestId);
break;
case 'progress':
if (request.onProgress) {
request.onProgress({ progress, completed, total });
}
break;
}
};
this.worker.onerror = (error) => {
console.error('Worker error:', error);
};
}
async findDomain(companyName) {
return new Promise((resolve, reject) => {
const requestId = ++this.requestId;
this.pendingRequests.set(requestId, { resolve, reject });
this.worker.postMessage({
type: 'findDomain',
requestId: requestId,
data: { companyName }
});
});
}
async findMultipleDomains(companies, options = {}) {
return new Promise((resolve, reject) => {
const requestId = ++this.requestId;
this.pendingRequests.set(requestId, {
resolve,
reject,
onProgress: options.onProgress
});
this.worker.postMessage({
type: 'findMultipleDomains',
requestId: requestId,
data: { companies, options }
});
});
}
terminate() {
if (this.worker) {
this.worker.terminate();
this.worker = null;
}
// Reject all pending requests
this.pendingRequests.forEach((request) => {
request.reject(new Error('Worker terminated'));
});
this.pendingRequests.clear();
}
}
// Usage example with progress tracking
async function demonstrateWebWorkerUsage() {
const finder = new BrowserDomainFinder('your-api-key');
try {
// Single lookup
const singleResult = await finder.findDomain('Apple Inc');
console.log('Single result:', singleResult);
// Bulk lookup with progress tracking
const companies = [
'Microsoft Corporation', 'Google LLC', 'Amazon Inc', 'Meta Platforms',
'Tesla Inc', 'Netflix Inc', 'Salesforce Inc', 'Oracle Corporation'
];
const bulkResults = await finder.findMultipleDomains(companies, {
concurrency: 3,
delayMs: 150,
onProgress: (progress) => {
console.log(`Progress: ${progress.progress.toFixed(1)}% (${progress.completed}/${progress.total})`);
// Update UI progress bar
const progressBar = document.getElementById('progress-bar');
if (progressBar) {
progressBar.style.width = `${progress.progress}%`;
}
}
});
console.log('Bulk results:', bulkResults);
} catch (error) {
console.error('Domain lookup failed:', error);
} finally {
finder.terminate();
}
}
// Advanced cache implementation for browser
class BrowserCache {
constructor(maxSize = 1000, ttlMs = 30 * 24 * 60 * 60 * 1000) { // 30 days
this.cache = new Map();
this.maxSize = maxSize;
this.ttlMs = ttlMs;
// Periodic cleanup
this.cleanupInterval = setInterval(() => this.cleanup(), 60 * 60 * 1000); // Every hour
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
// Check expiration
if (Date.now() > item.expires) {
this.cache.delete(key);
return null;
}
// Update access time for LRU
item.lastAccessed = Date.now();
return item.data;
}
set(key, data) {
const now = Date.now();
// Remove oldest items if cache is full
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
const oldestKey = this.getOldestKey();
if (oldestKey) {
this.cache.delete(oldestKey);
}
}
this.cache.set(key, {
data: data,
expires: now + this.ttlMs,
lastAccessed: now,
created: now
});
}
getOldestKey() {
let oldestKey = null;
let oldestTime = Infinity;
for (const [key, item] of this.cache) {
if (item.lastAccessed < oldestTime) {
oldestTime = item.lastAccessed;
oldestKey = key;
}
}
return oldestKey;
}
cleanup() {
const now = Date.now();
for (const [key, item] of this.cache) {
if (now > item.expires) {
this.cache.delete(key);
}
}
}
clear() {
this.cache.clear();
}
size() {
return this.cache.size;
}
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.clear();
}
}
// Enhanced domain finder with caching
class CachedBrowserDomainFinder extends BrowserDomainFinder {
constructor(apiKey, cacheOptions = {}) {
super(apiKey);
this.cache = new BrowserCache(cacheOptions.maxSize, cacheOptions.ttlMs);
}
async findDomain(companyName) {
const cacheKey = `domain:${companyName.toLowerCase().trim()}`;
// Check cache first
const cached = this.cache.get(cacheKey);
if (cached) {
return { ...cached, fromCache: true };
}
// Get from API
const result = await super.findDomain(companyName);
// Cache successful results with high confidence
if (result.success && result.data.confidence >= 70) {
this.cache.set(cacheKey, result);
}
return { ...result, fromCache: false };
}
terminate() {
super.terminate();
this.cache.destroy();
}
}
This Web Worker implementation enables heavy domain lookup operations without blocking the UI, providing smooth user experiences even when processing hundreds of companies.
Testing and Quality Assurance
Comprehensive testing ensures your domain finding implementation handles edge cases gracefully. Here’s a complete testing framework:
// test-framework.js
class DomainFinderTestSuite {
constructor(apiKey) {
this.finder = new CompanyDomainFinder(apiKey);
this.testResults = [];
}
async runAllTests() {
console.log('Starting Domain Finder Test Suite...');
const tests = [
this.testValidCompanies,
this.testInvalidInputs,
this.testEdgeCases,
this.testBulkOperations,
this.testRateHandling,
this.testErrorRecovery,
this.testCacheEfficiency
];
for (const test of tests) {
try {
await test.call(this);
this.logTest(test.name, 'PASS');
} catch (error) {
this.logTest(test.name, 'FAIL', error.message);
}
}
this.printSummary();
}
async testValidCompanies() {
const testCases = [
{ name: 'Apple Inc', expectedDomain: 'apple.com', minConfidence: 90 },
{ name: 'Microsoft Corporation', expectedDomain: 'microsoft.com', minConfidence: 90 },
{ name: 'Google LLC', expectedDomain: 'google.com', minConfidence: 90 },
{ name: 'Amazon Inc', expectedDomain: 'amazon.com', minConfidence: 90 }
];
for (const testCase of testCases) {
const result = await this.finder.findDomain(testCase.name);
if (!result.success) {
throw new Error(`Failed to find domain for ${testCase.name}: ${result.error}`);
}
if (result.data.confidence < testCase.minConfidence) {
throw new Error(`Low confidence for ${testCase.name}: ${result.data.confidence}%`);
}
if (!result.data.domain.includes(testCase.expectedDomain)) {
throw new Error(`Unexpected domain for ${testCase.name}: ${result.data.domain}`);
}
}
}
async testInvalidInputs() {
const invalidInputs = [
'',
null,
undefined,
123,
{},
'a',
'x'.repeat(500),
'!@#$%^&*()',
'nonexistentcompany12345'
];
for (const input of invalidInputs) {
try {
const result = await this.finder.findDomain(input);
// Should either throw an error or return unsuccessful result
if (result.success && input !== 'nonexistentcompany12345') {
throw new Error(`Unexpected success for invalid input: ${input}`);
}
} catch (error) {
// Expected for some invalid inputs
if (typeof input === 'string' && input.length > 0) {
throw error;
}
}
}
}
async testEdgeCases() {
const edgeCases = [
'apple', // Without Inc/Corp
'MICROSOFT CORPORATION', // All caps
' Google LLC ', // With whitespace
'Amazon.com Inc', // With .com in name
'Meta Platforms Inc', // Recently renamed company
'Tesla, Inc.' // With comma and period
];
for (const company of edgeCases) {
const result = await this.finder.findDomain(company);
if (!result.success) {
console.warn(`Edge case failed: ${company} - ${result.error}`);
} else {
console.log(`Edge case passed: ${company} -> ${result.data.domain}`);
}
}
}
async testBulkOperations() {
const companies = [
'Apple Inc', 'Microsoft Corporation', 'Google LLC',
'Amazon Inc', 'Meta Platforms', 'Tesla Inc'
];
const startTime = Date.now();
const results = await this.finder.findMultipleDomains(companies);
const endTime = Date.now();
if (results.length !== companies.length) {
throw new Error(`Expected ${companies.length} results, got ${results.length}`);
}
const successfulResults = results.filter(r => r.success);
if (successfulResults.length < companies.length * 0.8) {
throw new Error(`Low success rate: ${successfulResults.length}/${companies.length}`);
}
const totalTime = endTime - startTime;
const avgTimePerRequest = totalTime / companies.length;
console.log(`Bulk operation stats: ${totalTime}ms total, ${avgTimePerRequest.toFixed(2)}ms per request`);
}
async testRateHandling() {
// Test rapid sequential requests
const companies = ['Apple Inc', 'Microsoft Corporation', 'Google LLC'];
const promises = companies.map(company => this.finder.findDomain(company));
const results = await Promise.all(promises);
const successfulResults = results.filter(r => r.success);
if (successfulResults.length < companies.length) {
throw new Error('Rate limiting caused failures');
}
}
async testErrorRecovery() {
// Test with invalid API key
const invalidFinder = new CompanyDomainFinder('invalid-key');
try {
const result = await invalidFinder.findDomain('Apple Inc');
if (result.success) {
throw new Error('Expected failure with invalid API key');
}
// Should get proper error message
if (!result.error || typeof result.error !== 'string') {
throw new Error('Expected error message');
}
} catch (error) {
// Expected
}
}
async testCacheEfficiency() {
if (this.finder instanceof CachedBrowserDomainFinder) {
const company = 'Apple Inc';
// First request (should miss cache)
const result1 = await this.finder.findDomain(company);
if (result1.fromCache) {
throw new Error('First request should not be from cache');
}
// Second request (should hit cache)
const result2 = await this.finder.findDomain(company);
if (!result2.fromCache) {
throw new Error('Second request should be from cache');
}
// Results should be identical
if (result1.data.domain !== result2.data.domain) {
throw new Error('Cached result differs from original');
}
}
}
logTest(testName, status, error = null) {
const result = {
test: testName,
status: status,
error: error,
timestamp: new Date().toISOString()
};
this.testResults.push(result);
const statusColor = status === 'PASS' ? '\x1b[32m' : '\x1b[31m';
const resetColor = '\x1b[0m';
console.log(`${statusColor}[${status}]${resetColor} ${testName}${error ? ` - ${error}` : ''}`);
}
printSummary() {
const totalTests = this.testResults.length;
const passedTests = this.testResults.filter(r => r.status === 'PASS').length;
const failedTests = totalTests - passedTests;
console.log('\n=== Test Summary ===');
console.log(`Total Tests: ${totalTests}`);
console.log(`Passed: ${passedTests}`);
console.log(`Failed: ${failedTests}`);
console.log(`Success Rate: ${((passedTests / totalTests) * 100).toFixed(1)}%`);
if (failedTests > 0) {
console.log('\nFailed Tests:');
this.testResults
.filter(r => r.status === 'FAIL')
.forEach(r => console.log(` - ${r.test}: ${r.error}`));
}
}
}
// Run tests
async function runTests() {
const testSuite = new DomainFinderTestSuite(process.env.CUFINDER_API_KEY || 'your-api-key');
await testSuite.runAllTests();
}
// Export for use in testing frameworks
if (typeof module !== 'undefined' && module.exports) {
module.exports = { DomainFinderTestSuite };
}
Performance Monitoring and Analytics
Production applications need comprehensive monitoring to track API performance, identify bottlenecks, and optimize costs:
class DomainFinderAnalytics {
constructor() {
this.metrics = {
requestCount: 0,
successCount: 0,
cacheHits: 0,
averageResponseTime: 0,
confidenceDistribution: {},
errorTypes: {},
creditsUsed: 0,
dailyStats: {}
};
this.requestTimes = [];
this.maxStoredTimes = 1000; // Keep last 1000 request times
}
recordRequest(startTime, endTime, result) {
const responseTime = endTime - startTime;
// Update basic metrics
this.metrics.requestCount++;
if (result.success) {
this.metrics.successCount++;
// Track confidence distribution
const confidence = result.data.confidence;
const confidenceRange = this.getConfidenceRange(confidence);
this.metrics.confidenceDistribution[confidenceRange] =
(this.metrics.confidenceDistribution[confidenceRange] || 0) + 1;
// Track credits
if (result.data.creditsRemaining !== undefined) {
this.metrics.creditsUsed++;
}
} else {
// Track error types
const errorType = this.categorizeError(result.error);
this.metrics.errorTypes[errorType] = (this.metrics.errorTypes[errorType] || 0) + 1;
}
// Track cache hits
if (result.fromCache) {
this.metrics.cacheHits++;
}
// Update response time statistics
this.requestTimes.push(responseTime);
if (this.requestTimes.length > this.maxStoredTimes) {
this.requestTimes.shift();
}
this.metrics.averageResponseTime = this.requestTimes.reduce((a, b) => a + b, 0) / this.requestTimes.length;
// Update daily stats
const today = new Date().toISOString().split('T')[0];
if (!this.metrics.dailyStats[today]) {
this.metrics.dailyStats[today] = {
requests: 0,
successes: 0,
cacheHits: 0,
avgResponseTime: 0,
creditsUsed: 0
};
}
const dailyStat = this.metrics.dailyStats[today];
dailyStat.requests++;
if (result.success) {
dailyStat.successes++;
}
if (result.fromCache) {
dailyStat.cacheHits++;
}
if (!result.fromCache) {
dailyStat.creditsUsed++;
}
// Update daily average response time
const dailyTimes = this.requestTimes.slice(-dailyStat.requests);
dailyStat.avgResponseTime = dailyTimes.reduce((a, b) => a + b, 0) / dailyTimes.length;
}
getConfidenceRange(confidence) {
if (confidence >= 90) return '90-100%';
if (confidence >= 80) return '80-89%';
if (confidence >= 70) return '70-79%';
if (confidence >= 60) return '60-69%';
return 'Below 60%';
}
categorizeError(errorMessage) {
if (!errorMessage) return 'Unknown';
const error = errorMessage.toLowerCase();
if (error.includes('timeout') || error.includes('network')) return 'Network';
if (error.includes('401') || error.includes('unauthorized')) return 'Authentication';
if (error.includes('429') || error.includes('rate limit')) return 'Rate Limit';
if (error.includes('500') || error.includes('internal server')) return 'Server Error';
if (error.includes('400') || error.includes('bad request')) return 'Client Error';
return 'Other';
}
getPerformanceSummary() {
const successRate = this.metrics.requestCount > 0
? (this.metrics.successCount / this.metrics.requestCount) * 100
: 0;
const cacheHitRate = this.metrics.requestCount > 0
? (this.metrics.cacheHits / this.metrics.requestCount) * 100
: 0;
return {
totalRequests: this.metrics.requestCount,
successRate: `${successRate.toFixed(1)}%`,
cacheHitRate: `${cacheHitRate.toFixed(1)}%`,
avgResponseTime: `${this.metrics.averageResponseTime.toFixed(0)}ms`,
creditsUsed: this.metrics.creditsUsed,
confidenceDistribution: this.metrics.confidenceDistribution,
errorBreakdown: this.metrics.errorTypes,
p95ResponseTime: this.getPercentile(95),
p99ResponseTime: this.getPercentile(99)
};
}
getPercentile(percentile) {
if (this.requestTimes.length === 0) return 0;
const sorted = [...this.requestTimes].sort((a, b) => a - b);
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
return sorted[index];
}
exportMetrics() {
return {
summary: this.getPerformanceSummary(),
dailyStats: this.metrics.dailyStats,
timestamp: new Date().toISOString()
};
}
reset() {
this.metrics = {
requestCount: 0,
successCount: 0,
cacheHits: 0,
averageResponseTime: 0,
confidenceDistribution: {},
errorTypes: {},
creditsUsed: 0,
dailyStats: {}
};
this.requestTimes = [];
}
}
// Enhanced domain finder with analytics
class AnalyticsDomainFinder extends CompanyDomainFinder {
constructor(apiKey) {
super(apiKey);
this.analytics = new DomainFinderAnalytics();
}
async findDomain(companyName) {
const startTime = Date.now();
try {
const result = await super.findDomain(companyName);
const endTime = Date.now();
this.analytics.recordRequest(startTime, endTime, result);
return result;
} catch (error) {
const endTime = Date.now();
const errorResult = {
success: false,
error: error.message,
fromCache: false
};
this.analytics.recordRequest(startTime, endTime, errorResult);
throw error;
}
}
getAnalytics() {
return this.analytics.getPerformanceSummary();
}
exportAnalytics() {
return this.analytics.exportMetrics();
}
}
## Integration with Popular JavaScript Frameworks
### Vue.js Integration
For Vue.js applications, here's a complete composable that provides reactive domain lookup capabilities:
```javascript
// composables/useDomainFinder.js
import { ref, reactive, computed } from 'vue';
export function useDomainFinder(apiKey) {
const finder = new CompanyDomainFinder(apiKey);
const state = reactive({
isLoading: false,
error: null,
results: new Map(),
credits: null
});
const findDomain = async (companyName) => {
if (!companyName?.trim()) {
throw new Error('Company name is required');
}
const key = companyName.toLowerCase().trim();
// Return cached result if available
if (state.results.has(key)) {
return state.results.get(key);
}
state.isLoading = true;
state.error = null;
try {
const result = await finder.findDomain(companyName);
if (result.success) {
state.results.set(key, result.data);
state.credits = result.data.creditsRemaining;
return result.data;
} else {
state.error = result.error;
throw new Error(result.error);
}
} catch (error) {
state.error = error.message;
throw error;
} finally {
state.isLoading = false;
}
};
const findMultipleDomains = async (companies, options = {}) => {
state.isLoading = true;
state.error = null;
try {
const results = await finder.findMultipleDomains(companies, options);
// Cache successful results
results.forEach(result => {
if (result.success) {
const key = result.data.companyName.toLowerCase().trim();
state.results.set(key, result.data);
}
});
// Update credits from last result
const lastResult = results[results.length - 1];
if (lastResult.success) {
state.credits = lastResult.data.creditsRemaining;
}
return results;
} catch (error) {
state.error = error.message;
throw error;
} finally {
state.isLoading = false;
}
};
const clearCache = () => {
state.results.clear();
};
const getDomainFromCache = (companyName) => {
const key = companyName.toLowerCase().trim();
return state.results.get(key);
};
// Computed properties
const isLoading = computed(() => state.isLoading);
const error = computed(() => state.error);
const credits = computed(() => state.credits);
const cacheSize = computed(() => state.results.size);
return {
// Methods
findDomain,
findMultipleDomains,
clearCache,
getDomainFromCache,
// State
isLoading,
error,
credits,
cacheSize,
results: computed(() => Object.fromEntries(state.results))
};
}
// Vue component example
<template>
<div class="domain-finder">
<div class="input-section">
<input
v-model="companyName"
@keyup.enter="handleLookup"
placeholder="Enter company name..."
:disabled="isLoading"
class="company-input"
/>
<button @click="handleLookup" :disabled="isLoading || !companyName.trim()">
{{ isLoading ? 'Searching...' : 'Find Domain' }}
</button>
</div>
<div v-if="error" class="error">
{{ error }}
</div>
<div v-if="currentResult" class="result">
<h3>{{ currentResult.companyName }}</h3>
<a :href="currentResult.domain" target="_blank">
{{ currentResult.domain }}
</a>
<p>Confidence: {{ currentResult.confidence }}%</p>
</div>
<div class="stats">
<p>Cache Size: {{ cacheSize }}</p>
<p v-if="credits">Credits Remaining: {{ credits }}</p>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
import { useDomainFinder } from '@/composables/useDomainFinder';
const props = defineProps({
apiKey: {
type: String,
required: true
}
});
const companyName = ref('');
const currentResult = ref(null);
const {
findDomain,
isLoading,
error,
credits,
cacheSize,
getDomainFromCache
} = useDomainFinder(props.apiKey);
const handleLookup = async () => {
if (!companyName.value.trim()) return;
try {
// Check cache first
const cached = getDomainFromCache(companyName.value);
if (cached) {
currentResult.value = cached;
return;
}
const result = await findDomain(companyName.value);
currentResult.value = result;
} catch (err) {
currentResult.value = null;
}
};
// Watch for changes in company name to show cached results
watch(companyName, (newName) => {
if (newName.trim()) {
const cached = getDomainFromCache(newName);
if (cached) {
currentResult.value = cached;
} else {
currentResult.value = null;
}
} else {
currentResult.value = null;
}
});
</script>
Angular Service Integration
For Angular applications, here’s a complete service with RxJS integration:
// domain-finder.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, from, of, throwError } from 'rxjs';
import { map, catchError, tap, shareReplay, switchMap } from 'rxjs/operators';
export interface DomainResult {
success: boolean;
data?: {
companyName: string;
domain: string;
confidence: number;
creditsRemaining: number;
};
error?: string;
fromCache?: boolean;
}
@Injectable({
providedIn: 'root'
})
export class DomainFinderService {
private finder: CompanyDomainFinder;
private cache = new Map<string, DomainResult>();
private loadingSubject = new BehaviorSubject<boolean>(false);
private creditsSubject = new BehaviorSubject<number | null>(null);
public loading$ = this.loadingSubject.asObservable();
public credits$ = this.creditsSubject.asObservable();
constructor() {
// API key should be injected through environment or configuration
this.finder = new CompanyDomainFinder(environment.cufinderApiKey);
}
findDomain(companyName: string): Observable<DomainResult> {
if (!companyName?.trim()) {
return throwError(() => new Error('Company name is required'));
}
const cacheKey = companyName.toLowerCase().trim();
// Return cached result
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey)!;
return of({ ...cached, fromCache: true });
}
this.loadingSubject.next(true);
return from(this.finder.findDomain(companyName)).pipe(
map(result => {
if (result.success) {
this.creditsSubject.next(result.data.creditsRemaining);
// Cache successful results
this.cache.set(cacheKey, result);
}
return { ...result, fromCache: false };
}),
catchError(error => {
console.error('Domain lookup failed:', error);
return of({
success: false,
error: error.message,
fromCache: false
});
}),
tap(() => this.loadingSubject.next(false)),
shareReplay(1)
);
}
findMultipleDomains(companies: string[]): Observable<DomainResult[]> {
if (!companies?.length) {
return throwError(() => new Error('Companies array is required'));
}
this.loadingSubject.next(true);
return from(this.finder.findMultipleDomains(companies)).pipe(
map(results => {
// Cache successful results
results.forEach(result => {
if (result.success) {
const cacheKey = result.data.companyName.toLowerCase().trim();
this.cache.set(cacheKey, result);
}
});
// Update credits from last successful result
const lastSuccess = results.reverse().find(r => r.success);
if (lastSuccess) {
this.creditsSubject.next(lastSuccess.data.creditsRemaining);
}
return results.map(r => ({ ...r, fromCache: false }));
}),
catchError(error => {
console.error('Bulk lookup failed:', error);
return throwError(() => error);
}),
tap(() => this.loadingSubject.next(false))
);
}
searchDomainWithDebounce(companyName$: Observable<string>): Observable<DomainResult | null> {
return companyName$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(companyName => {
if (!companyName?.trim()) {
return of(null);
}
return this.findDomain(companyName).pipe(
catchError(() => of(null))
);
})
);
}
clearCache(): void {
this.cache.clear();
}
getCacheSize(): number {
return this.cache.size;
}
getCachedResult(companyName: string): DomainResult | null {
const cacheKey = companyName.toLowerCase().trim();
return this.cache.get(cacheKey) || null;
}
}
// Angular component example
// domain-lookup.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subject, takeUntil } from 'rxjs';
import { DomainFinderService, DomainResult } from './domain-finder.service';
@Component({
selector: 'app-domain-lookup',
template: `
<div class="domain-lookup">
<mat-form-field appearance="outline">
<mat-label>Company Name</mat-label>
<input matInput [formControl]="companyControl" placeholder="Enter company name...">
<mat-progress-bar *ngIf="loading$ | async" mode="indeterminate"></mat-progress-bar>
</mat-form-field>
<div *ngIf="result$ | async as result" class="result-card">
<mat-card>
<mat-card-header>
<mat-card-title>{{ result.data?.companyName }}</mat-card-title>
<mat-card-subtitle>
{{ result.data?.confidence }}% confidence
<mat-chip *ngIf="result.fromCache" color="accent">Cached</mat-chip>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<a [href]="result.data?.domain" target="_blank" mat-raised-button color="primary">
{{ result.data?.domain }}
</a>
</mat-card-content>
</mat-card>
</div>
<div class="stats">
<p>Credits: {{ credits$ | async }}</p>
<p>Cache Size: {{ cacheSize }}</p>
</div>
</div>
`
})
export class DomainLookupComponent implements OnInit, OnDestroy {
companyControl = new FormControl('');
result$ = new Subject<DomainResult | null>();
loading$ = this.domainService.loading$;
credits$ = this.domainService.credits$;
private destroy$ = new Subject<void>();
constructor(private domainService: DomainFinderService) {}
ngOnInit() {
// Set up reactive domain lookup with debouncing
this.domainService.searchDomainWithDebounce(this.companyControl.valueChanges)
.pipe(takeUntil(this.destroy$))
.subscribe(result => {
this.result$.next(result);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
get cacheSize() {
return this.domainService.getCacheSize();
}
}
Production Deployment Best Practices
When deploying JavaScript applications that use the Company Name to Domain API, consider these production-ready patterns:
Environment Configuration
// config/environment.js
const environments = {
development: {
cufinderApiKey: process.env.CUFINDER_API_KEY_DEV,
apiBaseUrl: 'https://api.cufinder.io/v2',
cacheEnabled: true,
cacheTTL: 24 * 60 * 60 * 1000, // 24 hours for dev
rateLimitDelay: 50,
maxConcurrentRequests: 3,
retryAttempts: 2,
timeout: 10000
},
staging: {
cufinderApiKey: process.env.CUFINDER_API_KEY_STAGING,
apiBaseUrl: 'https://api.cufinder.io/v2',
cacheEnabled: true,
cacheTTL: 7 * 24 * 60 * 60 * 1000, // 7 days
rateLimitDelay: 100,
maxConcurrentRequests: 5,
retryAttempts: 3,
timeout: 15000
},
production: {
cufinderApiKey: process.env.CUFINDER_API_KEY_PROD,
apiBaseUrl: 'https://api.cufinder.io/v2',
cacheEnabled: true,
cacheTTL: 30 * 24 * 60 * 60 * 1000, // 30 days
rateLimitDelay: 200,
maxConcurrentRequests: 10,
retryAttempts: 3,
timeout: 20000,
// Production-specific settings
enableAnalytics: true,
enableErrorReporting: true,
enablePerformanceMonitoring: true
}
};
const getConfig = () => {
const env = process.env.NODE_ENV || 'development';
return environments[env] || environments.development;
};
export default getConfig();
Error Reporting Integration
// utils/errorReporter.js
class ErrorReporter {
constructor(config = {}) {
this.config = config;
this.enableReporting = config.enableErrorReporting || false;
// Initialize error reporting service (e.g., Sentry, Bugsnag)
if (this.enableReporting && typeof window !== 'undefined') {
this.initializeSentry();
}
}
initializeSentry() {
// Example Sentry integration
if (window.Sentry) {
window.Sentry.init({
dsn: this.config.sentryDsn,
environment: this.config.environment,
beforeSend: (event) => this.filterEvent(event)
});
}
}
reportError(error, context = {}) {
console.error('Domain Finder Error:', error, context);
if (this.enableReporting) {
// Add domain finder specific context
const enrichedContext = {
...context,
module: 'domain-finder',
timestamp: new Date().toISOString(),
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'server',
url: typeof window !== 'undefined' ? window.location.href : 'server'
};
if (window.Sentry) {
window.Sentry.withScope((scope) => {
scope.setTag('module', 'domain-finder');
scope.setContext('domain-finder', enrichedContext);
window.Sentry.captureException(error);
});
}
}
}
reportPerformanceIssue(metric, context = {}) {
if (this.enableReporting && metric.value > metric.threshold) {
this.reportError(
new Error(`Performance threshold exceeded: ${metric.name}`),
{ ...context, metric }
);
}
}
filterEvent(event) {
// Filter out sensitive information
if (event.request?.url?.includes('api-key')) {
event.request.url = event.request.url.replace(/api-key=[^&]+/, 'api-key=***');
}
return event;
}
}
export default ErrorReporter;
Integration with CUFinder’s Comprehensive Platform
While this guide focuses on the Company Name to Domain API, CUFinder offers a comprehensive suite of data enrichment services. Here’s how to leverage the broader platform for maximum value:
// Complete CUFinder integration
class CUFinderPlatform {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.cufinder.io/v2';
// Initialize all available services
this.services = {
domainFinder: this.createDomainService(),
companyEnrichment: this.createCompanyEnrichmentService(),
emailFinder: this.createEmailFinderService(),
linkedinEnrichment: this.createLinkedInService(),
reverseEmailLookup: this.createReverseEmailService()
};
}
createDomainService() {
return {
findDomain: (companyName) => this.makeRequest('cuf', { company_name: companyName }),
findMultiple: (companies) => this.batchProcess(companies, 'cuf', 'company_name')
};
}
createCompanyEnrichmentService() {
return {
enrichCompany: (query) => this.makeRequest('enc', { query }),
getRevenue: (query) => this.makeRequest('car', { query }),
getFunding: (query) => this.makeRequest('elf', { query }),
getTechStack: (query) => this.makeRequest('fts', { query }),
getSubsidiaries: (query) => this.makeRequest('fcc', { query }),
getLookalikes: (query) => this.makeRequest('fcl', { query })
};
}
async makeRequest(endpoint, data) {
const formData = new URLSearchParams();
Object.entries(data).forEach(([key, value]) => {
formData.append(key, value);
});
try {
const response = await fetch(`${this.baseUrl}/${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'x-api-key': this.apiKey
},
body: formData
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
throw new Error(`API request failed: ${error.message}`);
}
}
async batchProcess(items, endpoint, paramName) {
const results = [];
const batchSize = 5;
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchPromises = batch.map(item =>
this.makeRequest(endpoint, { [paramName]: item })
);
const batchResults = await Promise.allSettled(batchPromises);
results.push(...batchResults);
// Rate limiting
if (i + batchSize < items.length) {
await new Promise(resolve => setTimeout(resolve, 200));
}
}
return results;
}
// Comprehensive company enrichment workflow
async enrichCompanyCompletely(companyName) {
const enrichmentData = {};
try {
// Step 1: Find company domain
const domainResult = await this.services.domainFinder.findDomain(companyName);
if (domainResult.status === 1) {
enrichmentData.domain = domainResult.data.domain;
enrichmentData.domainConfidence = domainResult.data.confidence_level;
}
// Step 2: Get comprehensive company data
const companyResult = await this.services.companyEnrichment.enrichCompany(companyName);
if (companyResult.status === 1) {
enrichmentData.companyDetails = companyResult.data.company;
}
// Step 3: Get financial information
const [revenueResult, fundingResult] = await Promise.allSettled([
this.services.companyEnrichment.getRevenue(companyName),
this.services.companyEnrichment.getFunding(companyName)
]);
if (revenueResult.status === 'fulfilled' && revenueResult.value.status === 1) {
enrichmentData.revenue = revenueResult.value.data.annual_revenue;
}
if (fundingResult.status === 'fulfilled' && fundingResult.value.status === 1) {
enrichmentData.funding = fundingResult.value.data.fundraising_info;
}
// Step 4: Get technology stack
if (enrichmentData.domain) {
const techResult = await this.services.companyEnrichment.getTechStack(companyName);
if (techResult.status === 1) {
enrichmentData.technologies = techResult.data.technologies;
}
}
// Step 5: Find similar companies
const lookalikeResult = await this.services.companyEnrichment.getLookalikes(companyName);
if (lookalikeResult.status === 1) {
enrichmentData.similarCompanies = lookalikeResult.data.companies.slice(0, 5);
}
return {
success: true,
data: enrichmentData,
enrichmentScore: this.calculateEnrichmentScore(enrichmentData)
};
} catch (error) {
return {
success: false,
error: error.message,
partialData: enrichmentData
};
}
}
calculateEnrichmentScore(data) {
let score = 0;
const weights = {
domain: 20,
companyDetails: 25,
revenue: 15,
funding: 15,
technologies: 10,
similarCompanies: 15
};
Object.entries(weights).forEach(([field, weight]) => {
if (data[field]) {
score += weight;
}
});
return score;
}
}
This comprehensive integration demonstrates how the Company Name to Domain API fits into CUFinder’s broader data enrichment ecosystem, enabling you to build complete business intelligence applications.
Getting Started with CUFinder’s JavaScript Integration
JavaScript developers now have everything needed to build sophisticated domain finding applications. From simple single-company lookups to complex enterprise workflows, the patterns and examples in this guide provide the foundation for production-ready implementations.
The power of JavaScript’s async nature, combined with CUFinder’s reliable API, makes it possible to create responsive user experiences that scale from individual users to enterprise applications processing thousands of companies daily. Whether you’re building React dashboards, Node.js microservices, or serverless functions, the implementation patterns shown here adapt to your specific architecture.
Beyond domain finding, CUFinder’s comprehensive API suite offers email discovery, company enrichment, technology stack detection, and 15+ other services that integrate seamlessly with the domain finding functionality you’ve learned here. For larger datasets, the enrichment engine provides a user-friendly interface that complements programmatic access.
Ready to transform your JavaScript applications with automated domain finding? Sign up for your CUFinder API key and start building more intelligent applications today. Your users will appreciate the instant, accurate website discovery that transforms manual research into automated insights.
⚡ Explore CUFinder APIs
Enrich people and companies at scale. Real-time endpoints for email, phone, revenue, tech stack, LinkedIn data, and more.



