The CORS Problem and Why a Proxy Is Needed
Modern Angular applications are typically built as single-page applications that run almost entirely in the browser. While this architecture gives a fast and fluid user experience, it introduces a challenge when the app needs to talk to a backend service. The browser's same-origin policy and CORS enforcement can get in the way of those requests.
CORS, or Cross-Origin Resource Sharing, is a browser security feature that stops web pages from making requests to a domain different from the one that served the page. That safeguard exists to prevent a malicious site from reading data that belongs to another site.
In a development environment, the mismatch is obvious: your Angular dev server listens on localhost:4200, while the backend API might be on localhost:8080 or an entirely separate host. Because browser requests carry the origin of localhost:4200, the backend replies with a response lacking the proper CORS headers, and the browser blocks it. The console shows something along the lines of "Cross-Origin Request Blocked" due to a missing Access-Control-Allow-Origin header.
Angular's proxyConfig: A Development-Time Gateway
To sidestep this obstacle during local work, Angular provides the proxyConfig option. It acts as a kind of reverse proxy that sits in front of the dev server and forwards matching requests to the actual backend.
Because the browser only ever talks to your Angular development server, every request has the same origin. That effectively removes the CORS barrier from the equation.
The flow is straightforward:
- The Angular app on
localhost:4200issues a call to/api/data. - The proxy layer catches that request.
- The proxy forwards it to
http://localhost:8080/api/data. - The backend processes the request and sends its response back to the proxy.
- The proxy hands the response back to the app.
From the browser's perspective, this appears to be a same-origin transaction. The CORS rules are never triggered, and you can continue developing your application without the usual friction.
Setting Up and Configuring proxyConfig
The proxy rules live in a configuration file at the root of your project. While the file name is up to you, most teams pick proxy.conf.json or proxy.conf.js. Inside that file, you define the forwarding rules for browser requests.
{
"/api": {
"target": "http://localhost:8080",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
The most common options you'll work with:
/api(Context): This is the URL prefix that triggers the proxy. Any request from your Angular application starting with/apiwill be intercepted and proxied. This is the key switch that decides which requests are forwarded instead of being handled locally. You can define several contexts, each mapping to a different backend server.target: The address the proxy will forward requests to. For example,http://localhost:8080points at a backend running on the local machine. This is the essential setting because it tells the proxy where to deliver the intercepted traffic.secure: This flag controls whether requests are sent over HTTPS. When set tofalse, SSL certificate validation is skipped, which is handy when your backend uses a self-signed certificate. Never usefalsein a production environment; it would expose your service to security risks.logLevel: Adjusts how much detail the proxy writes to the console. Setting it to"debug"shows you every request and response passing through the proxy, which aides in troubleshooting. Alternatives are"info","warn", and"error".changeOrigin: When enabled, theOriginheader of the forwarded request is changed to match the target URL. Backends that validate the origin field will see a value they know and trust. This is particularly important to prevent backend-side CORS complications.
Wiring proxyConfig Into the Angular Project
After the proxy file is ready, the next step is to let Angular know where to find it. That means updating the serve target inside angular.json.
{
"projects": {
"my-app": {
"architect": {
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"proxyConfig": "src/proxy.conf.json" // <-- Add this line
}
}
}
}
}
}
The important part is the proxyConfig key, added under the options object of the serve builder. The value should be the file path to your proxy configuration.
When you run ng serve, Angular reads this file and applies those rules to the development server.
Going Further with proxyConfig
A single context and target works fine for a simple setup, but the configuration grows along with your project's complexity. There are several capabilities to be aware of.
- Multiple Contexts: You can define multiple contexts to proxy different API endpoints to different backend servers.
{
"/products": {
"target": "http://localhost:3000",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
},
"/users": {
"target": "http://localhost:4000",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
- Path Rewrite: You can rewrite the URL path before forwarding the request to the backend. This helps when the backend API uses a different routing structure than your frontend code.
{
"/api": {
"target": "http://localhost:8080",
"secure": false,
"logLevel": "debug",
"changeOrigin": true,
"pathRewrite": {
"^/api": "" // Remove the /api prefix
}
}
}
In this case, a call to /api/products gets forwarded to http://localhost:8080/products without the /api prefix.
- WebSocket Proxying: Real-time updates via WebSockets can also run through the proxy. To enable that, you add a
wsfield to the rule.
{
"/ws": {
"target": "ws://localhost:8080",
"ws": true,
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
- Custom Proxy Logic: If the defaults are not enough, you can switch to a JavaScript-based configuration. That gives you full control, with a custom function handling the forwarding rules.
export default {
'/api/proxy': {
"target": 'http://localhost:3000',
"secure": false,
"bypass": function (req, res, proxyOptions) {
if (req.headers.accept.includes('html')) {
console.log('Skipping proxy for browser request.');
return '/index.html';
}
req.headers['X-Custom-Header'] = 'yes';
}
}
};
That setup highlights the bypass function, which lets you define logic for selectively skipping the proxy or handling requests specially.
The function gets three arguments:
-
req: The incoming request, as a Node.jshttp.IncomingMessage. This is where you can inspect method, headers, URL, or cookies. -
res: The outgoing response object (a Node.jshttp.ServerResponse). You can modify it, but do so with care, because an improper change could interfere with Angular's own server responses. -
proxyOptions: The configuration for this specific route. In most cases you won't need to use this directly.
proxyConfig Has No Place in Production
One point cannot be stressed enough: proxyConfig is strictly a development-time tool. It is not meant to run in a live environment, and it should never be deployed alongside your application.
For production deployments, the responsibility of handling reverse proxying belongs to your web server — whether that is Nginx, Apache, IIS, or another solution. This setup yields the best performance, maintains a strong security posture, and gives you full command over how requests are routed. If you were to run the dev server's proxy in a production context, you would introduce serious performance bottlenecks and expose your app to a range of security risks.
Wrapping Up
When your Angular application has to talk to backend services, proxyConfig is an essential part of a smooth development setup. Operating as a reverse proxy, it sidesteps CORS friction so you can concentrate on writing features rather than wrestling with cross-origin policies.
Once you are comfortable with the available options and more nuanced features, you can address a wide range of development scenarios without breaking stride. Just be certain to set up a proper reverse proxy on your production server and keep proxyConfig out of anything that is publicly accessible.
Getting good with proxyConfig is a reliable way to make your daily Angular workflow more productive while producing clean, dependable applications.
You can follow me on GitHub, where I'm creating cool projects.
I hope you enjoyed this article, don't forget to give ❤️.
Bye 👋
