No articles found
Try different keywords or browse our categories
How to Fix API & Environment Variables Works Locally but Not in Production Error & Issues
Learn how to fix APIs that work locally but fail in production, plus environment variables not working in production. Complete guide with solutions for deployment and configuration.
The ‘API works locally but not in production’ error and ‘environment variables not working in production’ are common issues developers face when deploying applications. These problems occur due to differences between local development and production environments, including environment variables, API endpoints, and server configurations.
This comprehensive guide provides complete solutions to resolve these issues with practical examples and deployment optimization techniques.
Understanding API and Environment Variable Issues
APIs that work perfectly in development often fail in production due to various environmental differences. These include:
- Different API endpoints between local and production
- Missing or incorrect environment variables in production
- Different server configurations and security policies
- Network connectivity issues in production environments
- CORS and security policy differences
Common Error Scenarios:
API call fails in production but works locallyEnvironment variable is undefined in productionConnection timeout in productionAuthentication failure in productionDatabase connection issues in production
Common Causes and Solutions
1. Environment Variable Issues
The most common cause is missing or incorrect environment variables in production.
❌ Problem Scenario:
// ❌ This works locally but fails in production
const apiUrl = process.env.API_URL; // May be undefined in production
const apiKey = process.env.API_KEY; // May be undefined in production
fetch(`${apiUrl}/data`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
})
.then(response => response.json())
.then(data => console.log(data));
✅ Solution: Proper Environment Handling
// ✅ Handle environment variables safely
const getApiConfig = () => {
const defaultConfig = {
apiUrl: 'https://api.example.com',
apiKey: null,
timeout: 10000
};
// ✅ Use environment variables with fallbacks
const config = {
apiUrl: process.env.REACT_APP_API_URL || process.env.API_URL || defaultConfig.apiUrl,
apiKey: process.env.REACT_APP_API_KEY || process.env.API_KEY || defaultConfig.apiKey,
timeout: parseInt(process.env.API_TIMEOUT || defaultConfig.timeout.toString())
};
// ✅ Validate required variables
if (!config.apiKey) {
console.warn('API key not configured - some features may not work');
}
return config;
};
// Usage
const config = getApiConfig();
fetch(`${config.apiUrl}/data`, {
headers: {
'Authorization': config.apiKey ? `Bearer ${config.apiKey}` : undefined,
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
console.error('API call failed:', error);
// Handle error gracefully
});
2. Different API Endpoints
Local and production environments often use different API endpoints.
❌ Problem Scenario:
// ❌ Hardcoded local endpoint
const API_BASE_URL = 'http://localhost:3001/api'; // ❌ Fails in production
fetch(`${API_BASE_URL}/users`)
.then(response => response.json())
.then(users => console.log(users));
✅ Solution: Dynamic API URLs
// ✅ Dynamic API URL based on environment
const getApiBaseUrl = () => {
if (typeof window !== 'undefined') {
// Browser environment
if (process.env.NODE_ENV === 'production') {
return process.env.REACT_APP_API_URL || 'https://api.yourdomain.com';
} else {
return process.env.REACT_APP_API_URL || 'http://localhost:3001';
}
} else {
// Server environment
if (process.env.NODE_ENV === 'production') {
return process.env.API_URL || 'https://api.yourdomain.com';
} else {
return process.env.API_URL || 'http://localhost:3001';
}
}
};
const API_BASE_URL = getApiBaseUrl();
// Usage
fetch(`${API_BASE_URL}/users`)
.then(response => response.json())
.then(users => console.log(users));
Solution 1: Proper Environment Configuration
Configure your application properly for different environments.
Environment-Specific Configuration:
// config/environment.js
class EnvironmentConfig {
static getEnvVar(name, defaultValue = null) {
// ✅ Handle different environment variable prefixes
const prefixes = ['', 'REACT_APP_', 'NEXT_PUBLIC_'];
for (const prefix of prefixes) {
const prefixedName = prefix + name;
const value = this.getValue(prefixedName);
if (value !== undefined && value !== null) {
return value;
}
}
return defaultValue;
}
static getValue(name) {
// ✅ Handle different ways environment variables can be accessed
if (typeof process !== 'undefined' && process.env) {
return process.env[name];
}
if (typeof window !== 'undefined' && window.env) {
return window.env[name];
}
if (typeof import.meta !== 'undefined' && import.meta.env) {
return import.meta.env[name];
}
return undefined;
}
static getApiConfig() {
return {
baseUrl: this.getEnvVar('API_URL', 'https://api.example.com'),
timeout: parseInt(this.getEnvVar('API_TIMEOUT', '10000')),
retries: parseInt(this.getEnvVar('API_RETRIES', '3')),
apiKey: this.getEnvVar('API_KEY'),
debug: this.getEnvVar('API_DEBUG', 'false') === 'true'
};
}
static isProduction() {
return this.getEnvVar('NODE_ENV', 'development') === 'production';
}
static isDevelopment() {
return !this.isProduction();
}
}
// Usage
const apiConfig = EnvironmentConfig.getApiConfig();
console.log('API Config:', apiConfig);
Configuration Validation:
// utils/configValidator.js
class ConfigValidator {
static validateApiConfig(config) {
const errors = [];
if (!config.baseUrl) {
errors.push('API_URL is required');
}
if (config.timeout && isNaN(config.timeout)) {
errors.push('API_TIMEOUT must be a number');
}
if (config.retries && isNaN(config.retries)) {
errors.push('API_RETRIES must be a number');
}
if (errors.length > 0) {
throw new Error(`Configuration validation failed: ${errors.join(', ')}`);
}
return true;
}
static validateEnvironment() {
const requiredVars = ['API_URL'];
const missingVars = requiredVars.filter(varName =>
!EnvironmentConfig.getEnvVar(varName)
);
if (missingVars.length > 0) {
throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`);
}
return true;
}
}
// Usage
try {
ConfigValidator.validateEnvironment();
const config = EnvironmentConfig.getApiConfig();
ConfigValidator.validateApiConfig(config);
} catch (error) {
console.error('Configuration error:', error.message);
// Handle configuration error appropriately
}
Solution 2: API Client with Error Handling
Create a robust API client that handles production environments properly.
Advanced API Client:
// services/ApiClient.js
class ApiClient {
constructor(config) {
this.config = {
baseUrl: config.baseUrl || 'https://api.example.com',
timeout: config.timeout || 10000,
retries: config.retries || 3,
apiKey: config.apiKey,
debug: config.debug || false
};
}
async request(endpoint, options = {}) {
const url = `${this.config.baseUrl}${endpoint}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
const defaultHeaders = {
'Content-Type': 'application/json',
};
if (this.config.apiKey) {
defaultHeaders['Authorization'] = `Bearer ${this.config.apiKey}`;
}
const requestOptions = {
...options,
headers: {
...defaultHeaders,
...options.headers
},
signal: controller.signal
};
try {
if (this.config.debug) {
console.log('API Request:', { url, options: requestOptions });
}
const response = await fetch(url, requestOptions);
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (this.config.debug) {
console.log('API Response:', data);
}
return data;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`Request timeout after ${this.config.timeout}ms`);
}
if (error.message.includes('CORS')) {
throw new Error('CORS error: Check your API server configuration');
}
throw error;
}
}
async get(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'GET' });
}
async post(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(data)
});
}
async put(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(data)
});
}
async delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}
// Usage
const apiConfig = EnvironmentConfig.getApiConfig();
const apiClient = new ApiClient(apiConfig);
// Example usage
apiClient.get('/users')
.then(users => console.log(users))
.catch(error => console.error('API Error:', error));
Solution 3: Environment-Specific Build Configuration
Configure your build process for different environments.
Webpack Configuration:
// webpack.config.js
const path = require('path');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
return {
mode: argv.mode || 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: isProduction ? '[name].[contenthash].js' : '[name].js',
},
plugins: [
// ✅ Define environment variables for build time
new (require('webpack').DefinePlugin)({
'process.env.NODE_ENV': JSON.stringify(argv.mode || 'development'),
'process.env.API_URL': JSON.stringify(
isProduction
? process.env.PROD_API_URL
: process.env.DEV_API_URL || 'http://localhost:3001'
),
'process.env.API_KEY': JSON.stringify(process.env.API_KEY),
}),
],
// ✅ Handle environment variables in development
devServer: {
environment: {
API_URL: 'http://localhost:3001',
},
},
};
};
Vite Configuration:
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig(({ mode }) => {
// ✅ Load environment variables based on mode
const env = {};
const envPrefixes = ['VITE_', 'REACT_APP_', 'NEXT_PUBLIC_'];
// Load environment variables
Object.keys(process.env).forEach(key => {
envPrefixes.forEach(prefix => {
if (key.startsWith(prefix)) {
env[key] = process.env[key];
}
});
});
return {
define: {
// ✅ Make environment variables available to the client
'process.env': JSON.stringify(env),
'import.meta.env': JSON.stringify(env),
},
// ✅ Configure for different environments
build: {
sourcemap: mode === 'development',
},
};
});
Solution 4: Server-Side Environment Handling
Handle environment variables properly on the server side.
Node.js Server Configuration:
// server.js
const express = require('express');
const app = express();
// ✅ Validate required environment variables on startup
const requiredEnvVars = ['PORT', 'API_URL', 'DATABASE_URL'];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
console.error(`Missing required environment variable: ${envVar}`);
process.exit(1);
}
}
// ✅ Set default values for optional variables
const config = {
port: process.env.PORT || 3000,
apiUrl: process.env.API_URL,
dbUrl: process.env.DATABASE_URL,
nodeEnv: process.env.NODE_ENV || 'development',
apiKey: process.env.API_KEY,
debug: process.env.DEBUG === 'true',
};
// ✅ Middleware to expose environment variables safely
app.use((req, res, next) => {
// ✅ Only expose safe environment variables to frontend
if (req.path.startsWith('/api/config')) {
res.json({
apiUrl: config.apiUrl,
nodeEnv: config.nodeEnv,
debug: config.debug
});
return;
}
next();
});
// ✅ API routes
app.get('/api/data', async (req, res) => {
try {
// ✅ Use environment variables for API calls
const response = await fetch(config.apiUrl + '/data', {
headers: {
'Authorization': config.apiKey ? `Bearer ${config.apiKey}` : undefined,
}
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
res.json(data);
} catch (error) {
console.error('API call failed:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(config.port, () => {
console.log(`Server running on port ${config.port} in ${config.nodeEnv} mode`);
});
Solution 5: Deployment Configuration
Configure your deployment properly for production environments.
Docker Configuration:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# ✅ Set environment variables in Docker
ENV NODE_ENV=production
ENV API_URL=https://api.yourdomain.com
EXPOSE 3000
CMD ["npm", "start"]
Environment Variables in Different Platforms:
Heroku:
# Set environment variables
heroku config:set API_URL=https://api.yourdomain.com
heroku config:set API_KEY=your-api-key
Vercel:
// vercel.json
{
"env": {
"API_URL": "https://api.yourdomain.com",
"API_KEY": "your-api-key"
}
}
Netlify:
# netlify.toml
[build.environment]
API_URL = "https://api.yourdomain.com"
API_KEY = "your-api-key"
Solution 6: Error Handling and Monitoring
Implement proper error handling for production environments.
Production-Ready Error Handling:
// utils/productionErrorHandler.js
class ProductionErrorHandler {
static async handleApiError(error, context = {}) {
console.error('API Error:', {
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'server',
url: typeof window !== 'undefined' ? window.location.href : 'server'
});
// ✅ Log to external service in production
if (EnvironmentConfig.isProduction()) {
await this.logToExternalService(error, context);
}
// ✅ Return user-friendly error message
return {
success: false,
error: 'An error occurred while processing your request',
code: error.code || 'UNKNOWN_ERROR'
};
}
static async logToExternalService(error, context) {
try {
await fetch('/api/logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
level: 'error',
message: error.message,
stack: error.stack,
context,
timestamp: new Date().toISOString(),
}),
});
} catch (logError) {
console.error('Failed to log error:', logError);
}
}
static async safeApiCall(apiFunction) {
try {
return await apiFunction();
} catch (error) {
return this.handleApiError(error);
}
}
}
// Usage
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
return ProductionErrorHandler.handleApiError(error, {
userId,
operation: 'fetchUserData'
});
}
}
Solution 7: Testing Production-Like Environments
Test your application in production-like environments before deployment.
Testing Configuration:
// tests/production.test.js
describe('Production Environment Tests', () => {
test('should handle missing environment variables gracefully', () => {
// ✅ Test with minimal environment variables
const originalEnv = process.env;
process.env = { ...originalEnv, API_URL: undefined };
try {
const config = EnvironmentConfig.getApiConfig();
expect(config.baseUrl).toBe('https://api.example.com'); // Should have fallback
} finally {
process.env = originalEnv;
}
});
test('should handle API timeouts properly', async () => {
// ✅ Test timeout handling
const apiClient = new ApiClient({
baseUrl: 'https://httpbin.org/delay/10', // This will timeout
timeout: 1000, // 1 second timeout
retries: 1
});
await expect(apiClient.get('/')).rejects.toThrow('Request timeout');
});
test('should validate configuration properly', () => {
// ✅ Test configuration validation
expect(() => {
ConfigValidator.validateApiConfig({ baseUrl: null });
}).toThrow('Configuration validation failed');
});
});
Environment Testing:
// scripts/test-production.js
const { spawn } = require('child_process');
function testProductionBuild() {
console.log('Testing production build...');
// ✅ Build for production
const buildProcess = spawn('npm', ['run', 'build'], {
stdio: 'inherit',
env: {
...process.env,
NODE_ENV: 'production',
API_URL: 'https://api.test.com' // Use test API URL
}
});
buildProcess.on('close', (code) => {
if (code === 0) {
console.log('Build successful! Testing locally...');
// ✅ Serve the build locally to test
const serveProcess = spawn('npx', ['serve', '-s', 'dist'], {
stdio: 'inherit'
});
console.log('Serving build at http://localhost:3000');
console.log('Press Ctrl+C to stop');
} else {
console.error('Build failed!');
process.exit(1);
}
});
}
// Run the test
testProductionBuild();
Solution 8: Debugging Production Issues
Implement debugging strategies for production environments.
Debugging Utilities:
// utils/productionDebugger.js
class ProductionDebugger {
static log(message, data = null, level = 'info') {
const logEntry = {
message,
data,
level,
timestamp: new Date().toISOString(),
environment: EnvironmentConfig.isProduction() ? 'production' : 'development',
};
// ✅ Only log in development or when explicitly enabled
if (EnvironmentConfig.isDevelopment() || process.env.DEBUG === 'true') {
console.log(`[${level.toUpperCase()}] ${message}`, data);
}
// ✅ Send to external logging service in production
if (EnvironmentConfig.isProduction() && level === 'error') {
this.sendToLoggingService(logEntry);
}
}
static async sendToLoggingService(logEntry) {
try {
await fetch('/api/logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(logEntry),
});
} catch (error) {
console.error('Failed to send log to service:', error);
}
}
static measurePerformance(name, fn) {
if (EnvironmentConfig.isDevelopment() || process.env.PERFORMANCE_LOGGING === 'true') {
const start = performance.now();
const result = fn();
const end = performance.now();
this.log(`Performance: ${name} took ${end - start} milliseconds`);
return result;
}
return fn();
}
}
// Usage
ProductionDebugger.log('API call started', { endpoint: '/users' });
const users = ProductionDebugger.measurePerformance('fetchUsers', () => {
return fetch('/api/users').then(r => r.json());
});
Performance Considerations
Optimized Production Configuration:
// utils/optimizedConfig.js
class OptimizedConfig {
constructor() {
this._config = null;
this._initialized = false;
}
async initialize() {
if (!this._initialized) {
// ✅ Load configuration once and cache it
this._config = {
apiUrl: EnvironmentConfig.getEnvVar('API_URL', 'https://api.example.com'),
timeout: parseInt(EnvironmentConfig.getEnvVar('API_TIMEOUT', '10000')),
retries: parseInt(EnvironmentConfig.getEnvVar('API_RETRIES', '3')),
apiKey: EnvironmentConfig.getEnvVar('API_KEY'),
debug: EnvironmentConfig.getEnvVar('DEBUG', 'false') === 'true'
};
// ✅ Validate configuration
ConfigValidator.validateApiConfig(this._config);
this._initialized = true;
}
return this._config;
}
get(key) {
if (!this._initialized) {
throw new Error('Config not initialized. Call initialize() first.');
}
return this._config[key];
}
}
// Usage
const configManager = new OptimizedConfig();
await configManager.initialize();
const apiUrl = configManager.get('apiUrl');
Security Considerations
Secure Environment Handling:
// utils/secureEnv.js
class SecureEnvironment {
static validateEnvironmentVariables() {
// ✅ Validate that sensitive variables are not exposed to frontend
const sensitiveVars = ['DB_PASSWORD', 'SECRET_KEY', 'PRIVATE_KEY'];
for (const varName of sensitiveVars) {
if (process.env[varName]) {
console.warn(`Sensitive environment variable ${varName} detected in environment`);
}
}
return true;
}
static sanitizeConfig(config) {
// ✅ Remove sensitive information from config before logging
const sanitized = { ...config };
if (sanitized.apiKey) {
sanitized.apiKey = '***REDACTED***';
}
if (sanitized.dbUrl) {
sanitized.dbUrl = sanitized.dbUrl.replace(/\/\/[^:]+:[^@]+@/, '//***:***@');
}
return sanitized;
}
static async secureApiCall(url, options = {}) {
try {
// ✅ Validate URL to prevent SSRF attacks
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== 'https:' && parsedUrl.hostname !== 'localhost') {
throw new Error('Only HTTPS URLs are allowed in production');
}
const response = await fetch(url, options);
// ✅ Validate response content type
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Invalid response content type');
}
return response;
} catch (error) {
console.error('Secure API call failed:', error);
throw error;
}
}
}
Common Mistakes to Avoid
1. Hardcoding URLs:
// ❌ Don't do this
const API_URL = 'http://localhost:3001/api'; // Will fail in production
2. Missing Environment Validation:
// ❌ Don't do this
const apiUrl = process.env.API_URL; // May be undefined
fetch(`${apiUrl}/data`); // Will fail if apiUrl is undefined
3. Exposing Sensitive Variables:
// ❌ Don't do this
window.env = process.env; // Exposes all environment variables
Alternative Solutions
Using React DevTools for Production:
// Component with environment detection
function EnvironmentDetector() {
const [envInfo, setEnvInfo] = useState({});
useEffect(() => {
const info = {
isProduction: EnvironmentConfig.isProduction(),
apiUrl: EnvironmentConfig.getEnvVar('API_URL'),
nodeEnv: EnvironmentConfig.getEnvVar('NODE_ENV'),
hasApiKey: !!EnvironmentConfig.getEnvVar('API_KEY')
};
setEnvInfo(info);
}, []);
return (
<div data-testid="env-detector">
<pre>{JSON.stringify(envInfo, null, 2)}</pre>
</div>
);
}
Feature Detection:
// Check for environment features
function checkEnvironmentFeatures() {
const features = {
hasProcess: typeof process !== 'undefined',
hasWindow: typeof window !== 'undefined',
hasEnv: typeof process !== 'undefined' && process.env,
isNode: typeof process !== 'undefined' && process.versions && process.versions.node,
isBrowser: typeof window !== 'undefined' && typeof window.document !== 'undefined'
};
return features;
}
Troubleshooting Checklist
When your API works locally but not in production:
- Check Environment Variables: Verify all required env vars are set in production
- Verify API Endpoints: Confirm API URLs are correct for production
- Test Network Connectivity: Ensure the production server can reach your API
- Review CORS Settings: Check CORS configuration for production domain
- Check Authentication: Verify API keys and tokens are properly configured
- Validate SSL Certificates: Ensure HTTPS certificates are valid
- Monitor Error Logs: Check server and browser error logs for details
Conclusion
The ‘API works locally but not in production’ and ‘environment variables not working in production’ errors occur due to differences between development and production environments. By implementing proper environment configuration, robust error handling, and thorough testing, you can resolve these issues and ensure your applications work seamlessly in both environments.
The key to resolving these issues is understanding the differences between environments, implementing proper configuration management, and testing your applications thoroughly before deployment. Whether you’re working with REST APIs, GraphQL, or other backend services, the solutions provided in this guide will help you create robust, production-ready applications.
Remember to always validate your environment variables, handle errors gracefully, and implement proper security measures to ensure your applications are secure and reliable in production environments.
Related Articles
Fix Vercel Deployment Failed Error: Complete Deployment Guide
Learn how to fix Vercel deployment failed errors. Complete guide with solutions for build failures, configuration issues, and deployment optimization.
How to Fix Access to Fetch Blocked by CORS Policy Error
Learn how to fix the Access to fetch blocked by CORS policy error. Complete guide with solutions for cross-origin requests, headers, and server configuration.
Fix: fetch failed or TypeError: Failed to fetch error in JavaScript
Learn how to fix the 'fetch failed' and 'TypeError: Failed to fetch' errors in JavaScript applications. This comprehensive guide covers network issues, CORS, and best practices.