Stop hardcoding API URLs and wrestling with CORS errorsβhere's the developer-friendly guide to Angular proxy configuration
Ever spent hours debugging CORS errors only to realize your API calls were hitting the wrong URL? Or worse, accidentally pushed production code with localhost:3000 hardcoded everywhere?
Here's a question for you: How many times have you opened the browser console to see that dreaded red text: "Access to XMLHttpRequest has been blocked by CORS policy"?
If you're nodding your head right now, you're in the right place. By the end of this article, you'll master Angular's proxy configuration and never have to worry about development API routing again. Plus, I'll show you some tricks that took me way too long to figure out.
Before we dive into the examples, a quick note: the code snippets provided here use syntax from earlier Angular versions.
Quick poll: Drop a comment belowβwhat's your biggest API configuration headache? Let's commiserate together!
What You'll Learn Today
- Why proxy configuration is a game-changer for local development
- Step-by-step setup that actually works (with real examples)
- How to handle multiple APIs like a pro
- Unit testing strategies for proxied endpoints
- Common pitfalls that will make you pull your hair out (and how to avoid them)
Ready? Let's dive in!
Why Do We Even Need a Proxy?
Picture this: You're building an Angular app that talks to a backend API. In production, everything lives on the same domain. But locally? Your Angular dev server runs on http://localhost:4200 while your API is on http://localhost:3000.
Enter the CORS monster.
Without a proxy, you're stuck with:
- CORS errors everywhere
- Hardcoded API URLs scattered across your codebase
- Different configurations for dev, staging, and production
- That one teammate who always forgets to change the URL back (we all know one)
The proxy is your superhero here. It intercepts API calls from your Angular app and forwards them to the right backendβno CORS drama, no URL juggling.
Setting Up Your First Proxy (The 5-Minute Version)
Let's get our hands dirty with some code. Here's the simplest proxy setup that'll solve 80% of your problems:
Step 1: Create the Proxy Configuration
Create a file called proxy.conf.json in your project root:
{
"/api": {
"target": "http://localhost:3000",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
}
}
What's happening here?
-
/api: Any request starting with/apigets proxied -
target: Where to forward the requests -
secure: Set tofalsefor local development (true for HTTPS) -
changeOrigin: Changes the origin header to match the target -
logLevel: Shows proxy activity in your console (super helpful for debugging!)
Step 2: Update Your Angular Configuration
Modify your angular.json file:
{
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "your-app:build",
"proxyConfig": "proxy.conf.json"
}
}
}
Step 3: Update Your Service Calls
Here's where the magic happens. In your Angular service:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserService {
// Notice: No hardcoded domain! Just the path
private apiUrl = '/api/users';
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
}
That's it! Run ng serve and your API calls will magically work without CORS issues.
Pro tip: Having issues? Check the console for proxy logs. The logLevel: "debug" setting is your best friend here.
Level Up: Handling Multiple APIs
Real-world apps rarely talk to just one API. Here's how to proxy multiple backends like a boss:
Advanced Proxy Configuration
Create a more sophisticated proxy.conf.js (yes, .js this time):
const PROXY_CONFIG = [
{
context: ['/api/v1'],
target: 'http://localhost:3000',
secure: false,
changeOrigin: true,
logLevel: 'debug'
},
{
context: ['/api/v2'],
target: 'http://localhost:3001',
secure: false,
changeOrigin: true,
pathRewrite: {
'^/api/v2': '/api' // Rewrites /api/v2/users to /api/users
}
},
{
context: ['/auth'],
target: 'https://auth.mycompany.com',
secure: true,
changeOrigin: true,
headers: {
'X-Custom-Header': 'MyApp'
}
}
];
module.exports = PROXY_CONFIG;
What's new here?
-
context: Array of paths to proxy (more flexible than single path) -
pathRewrite: Modify the URL before forwarding -
headers: Add custom headers to proxied requests
Update your angular.json to use the .js file:
"proxyConfig": "proxy.conf.js"
Question for you: How many different APIs does your current project talk to? Share in the commentsβI'm curious about your architecture!
Writing Unit Tests for Proxied Services
Here's something most tutorials skipβhow do you test services that use proxied endpoints? Let me show you:
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify(); // Ensure no outstanding requests
});
it('should fetch users from the proxied endpoint', () => {
const mockUsers = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
];
service.getUsers().subscribe(users => {
expect(users.length).toBe(2);
expect(users).toEqual(mockUsers);
});
// Note: We're testing against the relative URL, not the proxied target
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush(mockUsers);
});
it('should handle errors gracefully', () => {
const errorMessage = 'Server error';
service.getUsers().subscribe(
() => fail('Should have failed'),
error => {
expect(error.status).toBe(500);
}
);
const req = httpMock.expectOne('/api/users');
req.flush(errorMessage, { status: 500, statusText: 'Server Error' });
});
});
Key testing insights:
- Test against the relative URLs (
/api/users), not the proxied target - Use
HttpClientTestingModuleto mock HTTP calls - Always verify no outstanding requests with
httpMock.verify()
Common Pitfalls and How to Dodge Them
After helping dozens of developers debug proxy issues, here are the gotchas that trip everyone up:
1. The "It Works on My Machine" Problem
Issue: Proxy works locally but fails in Docker/CI.
Solution:
// proxy.conf.js
const target = process.env.API_URL || 'http://localhost:3000';
const PROXY_CONFIG = [
{
context: ['/api'],
target: target,
// ... rest of config
}
];
2. WebSocket Connections Failing
Issue: Real-time features break with proxy.
Solution:
{
"/socket.io": {
"target": "http://localhost:3000",
"ws": true, // Enable WebSocket proxy
"secure": false,
"changeOrigin": true
}
}
3. The Cookie Monster
Issue: Authentication cookies not being sent.
Solution:
{
context: ['/api'],
target: 'http://localhost:3000',
secure: false,
changeOrigin: true,
cookieDomainRewrite: "localhost", // Rewrite cookie domain
withCredentials: true
}
4. POST Requests Hanging Forever
Issue: Large POST requests timeout.
Solution:
{
context: ['/api'],
target: 'http://localhost:3000',
secure: false,
changeOrigin: true,
onProxyReq: (proxyReq, req, res) => {
// Fix body-parser issues
if (req.body) {
const bodyData = JSON.stringify(req.body);
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
}
}
}
If any of these saved you from a debugging nightmare, hit that clap button! Others need to see this too.
Bonus Tips for Power Users
1. Environment-Specific Proxy Configurations
Create multiple proxy configs for different environments:
βββ proxy/
β βββ proxy.local.conf.js
β βββ proxy.staging.conf.js
β βββ proxy.docker.conf.js
Then in package.json:
{
"scripts": {
"start:local": "ng serve --proxy-config proxy/proxy.local.conf.js",
"start:staging": "ng serve --proxy-config proxy/proxy.staging.conf.js",
"start:docker": "ng serve --proxy-config proxy/proxy.docker.conf.js"
}
}
2. Dynamic Proxy Bypass
Sometimes you need to skip the proxy for certain requests:
{
context: ['/api'],
target: 'http://localhost:3000',
bypass: function(req, res, proxyOptions) {
// Skip proxy for health checks
if (req.url === '/api/health') {
return '/health.json'; // Serve local file instead
}
// Skip proxy for specific headers
if (req.headers.accept?.includes('html')) {
return '/index.html';
}
}
}
3. Request/Response Interceptors
Debug or modify requests on the fly:
{
context: ['/api'],
target: 'http://localhost:3000',
onProxyReq: (proxyReq, req, res) => {
// Add authentication header
proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');
console.log('Proxying:', req.method, req.url);
},
onProxyRes: (proxyRes, req, res) => {
// Log response status
console.log('Response:', proxyRes.statusCode);
// Add custom header to response
proxyRes.headers['X-Proxy-Time'] = new Date().toISOString();
}
}
Quick Recap
Let's wrap up what we've covered:
asic proxy setup takes just 5 minutes and solves CORS issues instantly
Multiple API handling with advanced proxy configurations
Unit testing strategies that work with proxied endpoints
Common pitfalls and their battle-tested solutions
Power user tips for production-ready proxy setups
The proxy configuration might seem like a small detail, but it's one of those things that, once set up properly, makes your entire development experience smoother. No more CORS errors, no more hardcoded URLs, just clean, maintainable code.
Your Turn!
Alright, I've shown you my proxy tricksβnow I want to hear from you!
What's your biggest API integration challenge? Drop a comment below. I read every single one and often write follow-up articles based on your questions.
Did this save you from CORS hell? Smash that clap button (you can clap up to 50 timesβI won't judge if you go wild!)
Want more Angular deep-dives? I publish practical Angular guides every week. Follow me to get notified when the next one drops.
Challenge: Try setting up a proxy for your current project and share your before/after experience in the comments. Best story gets a shoutout in my next article!
Keep Learning
If you enjoyed this, you'll love these related articles:
- "Angular Performance: 5 OnPush Change Detection Tricks"
- "RxJS Patterns That Will Change How You Handle API Calls"
- "The Complete Guide to Angular Environments (Dev, Staging, Production)"
P.S. Still stuck with a proxy issue? Drop your specific problem in the comments with the hashtag #ProxyHelp and I'll personally help you debug it. We're all in this together!
Follow Me for More Angular & Frontend Goodness:
I regularly share hands-on tutorials, clean code tips, scalable frontend architecture, and real-world problem-solving guides.
- πΌ LinkedIn β Letβs connect professionally
- π₯ Threads β Short-form frontend insights
- π¦ X (Twitter) β Developer banter + code snippets
- π₯ BlueSky β Stay up to date on frontend trends
- π GitHub Projects β Explore code in action
- π Website β Everything in one place
- π Medium Blog β Long-form content and deep-dives
- π¬ Dev Blog β Free Long-form content and deep-dives
- βοΈ Substack β Weekly frontend stories & curated resources
- π§© Portfolio β Projects, talks, and recognitions
- βοΈ Hashnode β Developer blog posts & tech discussions
- βοΈ Reddit β Developer blog posts & tech discussions













