This guide provides a comprehensive Node.js implementation for integrating with JUHE API, featuring complete error handling and retry logic.
Prerequisites
- Node.js 12.x or higher
- npm or yarn package manager
- Basic understanding of JavaScript/TypeScript and async programming
Installation
First, install the required packages:
npm install axios dotenv node-cache
For TypeScript users (recommended):
npm install typescript @types/node @types/axios --save-dev
Client Implementation
Let's create a robust client for the JUHE API:
// juheAPI.js
const axios = require('axios');
const NodeCache = require('node-cache');
class JuheAPIClient {
constructor(apiKey, options = {}) {
if (!apiKey) {
throw new Error('API key is required');
}
this.apiKey = apiKey;
this.baseURL = '<https://hub.juheapi.com>';
this.timeout = options.timeout || 10000;
this.maxRetries = options.maxRetries || 3;
// Setup cache if enabled
this.cache = options.enableCache ?
new NodeCache({ stdTTL: options.cacheTTL || 300 }) : null;
// Create axios client
this.client = axios.create({
baseURL: this.baseURL,
timeout: this.timeout,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
// Request interceptor for authentication
this.client.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${this.apiKey}`;
if (config.method === 'get') {
config.params = config.params || {};
config.params.key = this.apiKey;
}
return config;
});
// Response interceptor for error handling
this.client.interceptors.response.use(
response => response.data,
error => this._handleRequestError(error)
);
}
async _handleRequestError(error) {
// Extract information from error
const { config, response } = error;
// Get error details
let statusCode = 500;
let errorCode = 'UNKNOWN_ERROR';
let errorMessage = 'An unknown error occurred';
if (response) {
statusCode = response.status;
if (response.data) {
errorCode = response.data.code || errorCode;
errorMessage = response.data.message || errorMessage;
}
} else if (error.code === 'ECONNABORTED') {
errorCode = 'TIMEOUT';
errorMessage = 'Request timed out';
} else if (error.code === 'ENOTFOUND') {
errorCode = 'CONNECTION_ERROR';
errorMessage = 'Could not connect to server';
}
// Implement retry logic for server errors
if (
config &&
(!config.__retryCount || config.__retryCount < this.maxRetries) &&
(!response || response.status >= 500 || response.status === 429)
) {
// Initialize or increment retry count
config.__retryCount = (config.__retryCount || 0) + 1;
// Exponential backoff with jitter
const delay = Math.min(
Math.pow(2, config.__retryCount) * 100 + Math.random() * 100,
3000
);
// Wait and retry
await new Promise(resolve => setTimeout(resolve, delay));
return this.client(config);
}
// If we've exhausted retries or shouldn't retry, throw a nice error
const apiError = new Error(errorMessage);
apiError.code = errorCode;
apiError.statusCode = statusCode;
apiError.response = response;
throw apiError;
}
// Base HTTP methods
async get(endpoint, params = {}, options = {}) {
const cacheKey = options.cacheKey || `${endpoint}:${JSON.stringify(params)}`;
// Check cache
if (this.cache && !options.skipCache) {
const cachedData = this.cache.get(cacheKey);
if (cachedData) return cachedData;
}
const response = await this.client.get(endpoint, { params });
// Cache the response
if (this.cache && !options.skipCache) {
this.cache.set(cacheKey, response);
}
return response;
}
async post(endpoint, data = {}, params = {}) {
return this.client.post(endpoint, data, { params });
}
async put(endpoint, data = {}, params = {}) {
return this.client.put(endpoint, data, { params });
}
async delete(endpoint, params = {}) {
return this.client.delete(endpoint, { params });
}
// API-specific methods
async getWeather(location) {
return this.get('/weather/current', { location });
}
async getWeatherForecast(location, days = 5) {
return this.get('/weather/forecast', { location, days });
}
async batch(requests) {
return this.post('/batch', { requests });
}
}
module.exports = JuheAPIClient;
Basic Usage Example
Here's how to use the client in a simple application:
// app.js
require('dotenv').config();
const JuheAPIClient = require('./juheAPI');
const client = new JuheAPIClient(process.env.JUHE_API_KEY, {
enableCache: true,
cacheTTL: 300, // 5 minutes
timeout: 5000 // 5 seconds
});
async function main() {
try {
// Get current weather for London
const weather = await client.getWeather('London');
console.log('Current weather in London:');
console.log(`Temperature: ${weather.data.current.temp_c}°C`);
console.log(`Condition: ${weather.data.current.condition.text}`);
// Get weather forecast for New York
const forecast = await client.getWeatherForecast('New York', 3);
console.log('\\n3-day forecast for New York:');
forecast.data.forecast.forEach(day => {
console.log(`${day.date}: ${day.condition} (${day.min_temp_c}°C - ${day.max_temp_c}°C)`);
});
} catch (error) {
console.error(`Error: ${error.code} - ${error.message}`);
if (error.statusCode === 429) {
console.error('Rate limit exceeded. Please try again later.');
}
}
}
main();
Express.js Integration
Integrate the JUHE API with an Express.js application:
// server.js
const express = require('express');
const JuheAPIClient = require('./juheAPI');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 3000;
// Create JUHE API client
const juheClient = new JuheAPIClient(process.env.JUHE_API_KEY, {
enableCache: true
});
// Weather endpoint
app.get('/api/weather/:location', async (req, res) => {
try {
const { location } = req.params;
const weather = await juheClient.getWeather(location);
res.json({
success: true,
data: {
location: weather.data.location.name,
temperature: weather.data.current.temp_c,
condition: weather.data.current.condition.text,
humidity: weather.data.current.humidity
}
});
} catch (error) {
res.status(error.statusCode || 500).json({
success: false,
error: {
code: error.code,
message: error.message
}
});
}
});
// Forecast endpoint
app.get('/api/weather/:location/forecast', async (req, res) => {
try {
const { location } = req.params;
const { days = 5 } = req.query;
const forecast = await juheClient.getWeatherForecast(
location,
parseInt(days, 10)
);
res.json({
success: true,
data: {
location: forecast.data.location.name,
forecast: forecast.data.forecast.map(day => ({
date: day.date,
condition: day.condition,
minTemp: day.min_temp_c,
maxTemp: day.max_temp_c,
chanceOfRain: day.chance_of_rain
}))
}
});
} catch (error) {
res.status(error.statusCode || 500).json({
success: false,
error: {
code: error.code,
message: error.message
}
});
}
});
app.listen(port, () => {
console.log(`Server running at <http://localhost>:${port}`);
});
Batch Processing Example
Process multiple API requests efficiently:
async function getMultipleLocations(locations) {
try {
// Build batch requests
const requests = locations.map(location => ({
method: 'GET',
path: '/weather/current',
params: { location }
}));
console.log(`Fetching weather for ${locations.length} locations in a single batch...`);
// Send batch request
const response = await client.batch(requests);
// Process results
const results = response.results.map((result, index) => {
if (result.status === 'success') {
const weather = result.data;
return {
location: locations[index],
temperature: weather.current.temp_c,
condition: weather.current.condition.text
};
} else {
return {
location: locations[index],
error: `Error: ${result.code} - ${result.message}`
};
}
});
return results;
} catch (error) {
console.error(`Batch error: ${error.message}`);
throw error;
}
}
// Usage
getMultipleLocations(['London', 'New York', 'Tokyo', 'Sydney'])
.then(results => {
results.forEach(result => {
if (result.error) {
console.log(`${result.location}: ${result.error}`);
} else {
console.log(`${result.location}: ${result.temperature}°C, ${result.condition}`);
}
});
})
.catch(error => console.error('Failed to fetch locations:', error.message));
React Integration with Hooks
For frontend applications using React:
// useJuheAPI.js
import { useState, useEffect, useCallback } from 'react';
// Create a simple frontend API client
function createClient(apiKey) {
const baseURL = '<https://hub.juheapi.com>';
return {
async get(endpoint, params = {}) {
// Add API key to params
const queryParams = new URLSearchParams({
...params,
key: apiKey
});
const response = await fetch(`${baseURL}${endpoint}?${queryParams}`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiKey}`
}
});
const data = await response.json();
if (!response.ok || data.status === 'error') {
const error = new Error(data.message || 'API request failed');
error.code = data.code;
error.status = response.status;
throw error;
}
return data;
}
};
}
// Weather hook
export function useWeather(location, apiKey) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchWeather = useCallback(async () => {
if (!location) return;
setLoading(true);
setError(null);
try {
const client = createClient(apiKey);
const result = await client.get('/weather/current', { location });
setData(result.data);
} catch (err) {
setError({
message: err.message,
code: err.code
});
} finally {
setLoading(false);
}
}, [location, apiKey]);
useEffect(() => {
fetchWeather();
}, [fetchWeather]);
return { data, loading, error, refetch: fetchWeather };
}
// Usage in a React component
function WeatherDisplay({ location }) {
const API_KEY = process.env.REACT_APP_JUHE_API_KEY;
const { data, loading, error } = useWeather(location, API_KEY);
if (loading) return <p>Loading weather data...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!data) return <p>No weather data available</p>;
return (
<div className="weather-card">
<h2>{data.location.name}</h2>
<div className="temperature">{data.current.temp_c}°C</div>
<div className="condition">{data.current.condition.text}</div>
<div className="details">
<p>Humidity: {data.current.humidity}%</p>
<p>Wind: {data.current.wind_kph} km/h</p>
</div>
</div>
);
}
Error Handling Best Practices
Our client implements several error handling best practices:
- Custom Error Objects: With status code, error code, and message
- Automatic Retries: Using exponential backoff for transient errors
- Rate Limiting Handling: Special handling for 429 responses
- Timeouts: Configurable timeout settings
- Consistent Formatting: Standard error response format
Error handling example:
async function safeApiCall() {
try {
const result = await client.getWeather('London');
return result;
} catch (error) {
// Handle specific error codes
switch (error.code) {
case 'RATE_LIMIT_EXCEEDED':
console.error('Rate limit hit. Trying again later...');
// Implement delayed retry or fallback
break;
case 'INVALID_API_KEY':
console.error('API key invalid. Check configuration.');
// Alert administrator
break;
case 'TIMEOUT':
console.error('Request timed out. Network may be slow.');
// Retry with increased timeout
break;
default:
console.error(`Unexpected error: ${error.code} - ${error.message}`);
}
// Return fallback data or rethrow
throw error;
}
}
Next Steps
- Explore Best Practices for API integration
- Check out Java Integration examples
- Learn about Advanced Topics for optimization