What Are Proxies and Why Do We Need Them?
When building full stack applications, you typically run two separate servers:
- A frontend server (e.g., running on
http://localhost:5173with Vite) - A backend server (e.g., running on
http://localhost:5000)
This separation creates a challenge: how does your frontend communicate with your backend during development?
The Restaurant Analogy
Think of your application as a restaurant. The frontend is the dining area where customers (users) interact with your service. The backend is the kitchen where the actual food preparation (data processing) happens.
A proxy is like the waiter who takes orders from the dining area and delivers them to the kitchen, then returns with the prepared food. Without this intermediary, customers would need to walk into the kitchen themselves to place and collect orders—a chaotic and inefficient process!
The Challenge Without Proxies
Let's imagine we're building a cat rental application (because who wouldn't want to rent a cat for the day?). Our backend API has an endpoint at /api/cats that returns a list of available cats.
Option 1: Hard-Coding Full URLs (Not Recommended)
// In your frontend code
fetch('http://localhost:5000/api/cats')
.then(response => response.json())
.then(cats => console.log(cats));
Problem: When you deploy to production, your backend won't be at http://localhost:5000 anymore. You'd need to update every single API call in your codebase!
Option 2: Using Relative URLs (Without Proxy)
// In your frontend code
fetch('/api/cats')
.then(response => response.json())
.then(cats => console.log(cats));
Problem: This request will go to http://localhost:5173/api/cats (your frontend server), but your backend is at http://localhost:5000. The request will fail!
Real-World Scenario
Consider a team building an e-commerce platform. The frontend developers are working with React and Vite, while the backend team is building an Express API. Without a proxy, the frontend team would either need to:
- Hardcode backend URLs, creating technical debt and deployment headaches
- Implement complex environment-specific configuration
- Deal with cross-origin resource sharing (CORS) issues
Enter the Proxy: The Elegant Solution
A development proxy in Vite acts as a middleman that intercepts specific requests from your frontend and forwards them to your backend server.
The Mail Forwarding Analogy
Think of a proxy like a mail forwarding service. When you move to a new address, you can set up mail forwarding so that letters sent to your old address automatically get redirected to your new one.
Similarly, a proxy in Vite takes requests sent to certain paths on your frontend server and automatically forwards them to your backend server.
How It Works
- Your frontend code makes a request to
/api/cats - The request is sent to your frontend server at
http://localhost:5173/api/cats - The proxy intercepts this request because it matches the configured pattern (
/api) - The proxy forwards the request to
http://localhost:5000/api/cats - Your backend server processes the request and sends back a response
- The proxy receives the response and forwards it back to your frontend code
Setting Up a Proxy in Vite
Configuring a proxy in Vite is straightforward. You'll need to modify your Vite configuration file.
Step-by-Step Guide
Locate Your Vite Configuration File
First, find your Vite configuration file. It's typically located at frontend/vite.config.js in your project root.
Modify the Configuration to Add a Proxy
Open the file and add the proxy configuration:
// frontend/vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' // or whatever framework plugin you're using
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": "http://localhost:5000"
}
}
});
This configuration tells Vite to forward any request that starts with /api to http://localhost:5000.
Using a Function-Based Configuration
If you need different configurations based on the environment or command, you can use a function:
// frontend/vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ mode }) => ({
plugins: [react()],
server: {
proxy: {
"/api": "http://localhost:5000"
}
}
}));
This allows you to conditionally set different proxy configurations based on the mode parameter (e.g., development vs. production).
Now Make API Requests in Your Frontend Code
With the proxy configured, you can now use relative URLs in your frontend code:
// In your frontend component
async function fetchCats() {
try {
const response = await fetch('/api/cats');
const cats = await response.json();
return cats;
} catch (error) {
console.error('Error fetching cats:', error);
}
}
This code will work correctly both in development and production environments!
Hands-On Exercise: Building a Weather Dashboard
Let's put our knowledge into practice by building a simple weather dashboard that fetches data from a backend API.
Project Setup
Create a new Vite project and set up the folder structure:
mkdir weather_dashboard
cd weather_dashboard
npm create vite@latest frontend -- --template react
cd frontend
npm install
cd ..
mkdir backend
cd backend
npm init -y
npm install express cors
touch server.js
Backend Implementation (backend/server.js)
// backend/server.js
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = 5000;
app.use(cors());
app.use(express.json());
// Mock weather data
const weatherData = {
'new-york': { temperature: 20, conditions: 'Sunny', humidity: 65 },
'london': { temperature: 15, conditions: 'Rainy', humidity: 80 },
'tokyo': { temperature: 28, conditions: 'Clear', humidity: 70 },
'sydney': { temperature: 25, conditions: 'Partly Cloudy', humidity: 60 }
};
app.get('/api/weather/:city', (req, res) => {
const city = req.params.city.toLowerCase();
if (weatherData[city]) {
// Simulate network delay
setTimeout(() => {
res.json(weatherData[city]);
}, 500);
} else {
res.status(404).json({ error: 'City not found' });
}
});
app.listen(PORT, () => {
console.log(`Backend server running on http://localhost:${PORT}`);
});
Configure Vite Proxy (frontend/vite.config.js)
// frontend/vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": "http://localhost:5000"
}
}
});
Frontend Implementation (frontend/src/App.jsx)
// frontend/src/App.jsx
import { useState, useEffect } from 'react'
import './App.css'
function App() {
const [city, setCity] = useState('new-york');
const [weather, setWeather] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchWeather = async (selectedCity) => {
setLoading(true);
setError(null);
try {
// Notice we're using a relative URL here!
const response = await fetch(`/api/weather/${selectedCity}`);
if (!response.ok) {
throw new Error('City not found');
}
const data = await response.json();
setWeather(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchWeather(city);
}, [city]);
return (
Weather Dashboard
{loading && Loading weather data...
}
{error && Error: {error}
}
{weather && !loading && (
{city.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}
{weather.temperature}°C
{weather.conditions}
Humidity: {weather.humidity}%
)}
)
}
export default App
Running the Application
Open two terminal windows:
Terminal 1 (Backend):
cd backend
node server.js
Terminal 2 (Frontend):
cd frontend
npm run dev
Now you can open your browser to the URL shown in the frontend terminal (usually http://localhost:5173) and see your weather dashboard in action!
What's Happening Here?
When you select a city in the dropdown:
- The frontend makes a fetch request to
/api/weather/cityname - Vite's development server receives this request at
http://localhost:5173/api/weather/cityname - The proxy configuration detects the
/apiprefix and forwards the request tohttp://localhost:5000/api/weather/cityname - The backend processes the request and sends back the weather data
- The proxy passes this response back to your frontend application
- The weather data is displayed in the UI
All of this happens seamlessly, and your frontend code doesn't need to know anything about where the backend is actually located!
Troubleshooting Common Proxy Issues
Proxy Not Working
Symptom: Your requests are not being proxied correctly.
Possible Solution:
- Make sure your backend server is actually running
- Verify that your proxy configuration is correct
- Check that you're making requests to paths that match your proxy configuration
- Restart your Vite development server after changing the configuration
CORS Errors Despite Proxy
Symptom: You're still seeing CORS errors in your browser console.
Possible Solution:
- Add
changeOrigin: trueto your proxy configuration - Make sure your backend is not explicitly checking for specific origins
// frontend/vite.config.js
export default defineConfig({
server: {
proxy: {
"/api": {
target: "http://localhost:5000",
changeOrigin: true
}
}
}
});
WebSocket Connections Not Proxied
Symptom: WebSocket connections fail when using a proxy.
Possible Solution:
- Add
ws: trueto your proxy configuration for WebSocket endpoints
// frontend/vite.config.js
export default defineConfig({
server: {
proxy: {
"/socket.io": {
target: "ws://localhost:5000",
ws: true
}
}
}
});
From Development to Production
It's important to understand that Vite's proxy configuration is for development only. In production, you'll need a different approach.
Common Production Setups
- Server-side proxy: Configure Nginx, Apache, or another web server to proxy API requests
- Same-origin deployment: Serve your frontend from your backend server (e.g., Express serving React builds)
- API Gateway: Use a cloud service like AWS API Gateway to route requests
- Backend-for-Frontend (BFF) pattern: Create a dedicated backend service that proxies requests to other services
Production Example: Nginx Configuration
In production, you might use Nginx as a reverse proxy:
server {
listen 80;
server_name mydomain.com;
# Serve static frontend files
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
# Proxy API requests to the backend
location /api {
proxy_pass http://backend-service:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Conclusion
Setting up a proxy in Vite is a simple yet powerful technique that solves the common challenge of connecting your frontend and backend during development. By using relative URLs with a properly configured proxy, you can write clean, maintainable code that works seamlessly across different environments.
Remember the key benefits of using a proxy:
- Keep your frontend code environment-agnostic
- Avoid hardcoding backend URLs
- Bypass CORS issues during development
- Simplify deployment to different environments
As your applications grow in complexity, you might need more advanced proxy configurations or even dedicated API gateways. But for most full stack applications, the simple proxy setup described in this tutorial will serve you well.