Angular Development Proxy — Eliminating CORS in Local Development

The Angular development proxy eliminates CORS entirely during local development by making the Angular dev server act as a proxy — routing /api/* requests to the ASP.NET Core API. From the browser’s perspective, all requests go to localhost:4200 (the same origin), so no CORS headers are needed. This is simpler than configuring CORS for every developer’s local setup and mirrors production where the reverse proxy (nginx, Azure Front Door) routes requests.

Angular Proxy Configuration

// ── proxy.conf.json — root of the Angular project ─────────────────────────
{
  "/api": {
    "target": "http://localhost:5000",   // ASP.NET Core API address
    "secure": false,                      // allow self-signed certs in dev
    "changeOrigin": true,                 // rewrite the Origin header
    "logLevel": "debug"                   // log proxied requests to console
  },
  "/hubs": {
    "target": "http://localhost:5000",   // SignalR hubs
    "secure": false,
    "changeOrigin": true,
    "ws": true                            // enable WebSocket proxying for SignalR
  }
}

// With path rewriting (if API prefix differs):
// "/api": {
//   "target": "http://localhost:5000",
//   "pathRewrite": { "^/api": "" }      // strip /api prefix before forwarding
// }
// ── angular.json — register the proxy config ──────────────────────────────
// "serve": {
//   "options": {
//     "proxyConfig": "proxy.conf.json"   // ← add this line
//   }
// }

// ── How it works ──────────────────────────────────────────────────────────
// Browser:   GET http://localhost:4200/api/posts
// ↓ Angular dev server intercepts /api/* requests
// ↓ Forwards to: GET http://localhost:5000/api/posts
// ↓ Response returned to browser as if from localhost:4200
// Browser sees: same origin (localhost:4200) — no CORS needed!

// ── Environment config — use relative URL in development ──────────────────
// src/environments/environment.ts:
// export const environment = {
//   apiUrl: '',   // empty — /api/posts resolved relative to dev server origin
//   ...
// };

// src/environments/environment.prod.ts:
// export const environment = {
//   apiUrl: 'https://api.blogapp.com',  // absolute URL for production
//   ...
// };

// ── Service uses relative URL in dev, absolute in production ──────────────
@Injectable({ providedIn: 'root' })
export class PostsApiService {
  private config  = inject(APP_CONFIG);
  private http    = inject(HttpClient);
  // In dev:  this.config.apiUrl = '' → '/api/posts' (proxied)
  // In prod: this.config.apiUrl = 'https://api.blogapp.com' → 'https://api.blogapp.com/api/posts'
  private baseUrl = `${this.config.apiUrl}/api/posts`;
}

// ── Multiple targets for microservices ────────────────────────────────────
// proxy.conf.json:
// {
//   "/api/posts":   { "target": "http://localhost:5001" },  // Posts API
//   "/api/users":   { "target": "http://localhost:5002" },  // Users API
//   "/api/comments":{ "target": "http://localhost:5003" }   // Comments API
// }
Note: The Angular proxy runs only in the development server (ng serve) — it has no effect on production builds. When you run ng build, the proxy configuration is ignored and the built app makes direct HTTP requests to the configured API URL. This is why the environment file uses an empty apiUrl for development (relying on the proxy) and the full absolute API URL for production. The proxy bridges the dev-to-prod gap cleanly.
Tip: Enable "logLevel": "debug" in the proxy config during initial development to see every proxied request in the Angular dev server terminal output. This confirms the proxy is working — you can see exactly which URL the request was forwarded to and the response status. Once the proxy is working reliably, switch to "logLevel": "warn" to reduce console noise during normal development.
Warning: The proxy does not work for requests made from ng build output served by a static web server (like http-server or VS Code Live Server). The proxy is a feature of Angular’s Webpack dev server. For integration testing with a built Angular app, configure CORS on the API or use nginx as a reverse proxy locally. Do not confuse “the proxy works in ng serve” with “CORS is configured” — they are separate mechanisms for different scenarios.

Common Mistakes

Mistake 1 — Not adding ws: true for SignalR WebSocket proxying (connection falls back to long polling)

❌ Wrong — proxy target without "ws": true; WebSocket upgrade fails; SignalR falls back to long polling.

✅ Correct — "ws": true in the proxy target for any path used by SignalR hubs.

Mistake 2 — Using the proxy URL (empty apiUrl) in production build (requests go nowhere)

❌ Wrong — environment.prod.ts has empty apiUrl; production app calls /api/posts which 404s on the CDN.

✅ Correct — environment.prod.ts has the full absolute production API URL.

🧠 Test Yourself

The Angular dev server proxy is configured to forward /api/* to http://localhost:5000. Does the ASP.NET Core API need CORS configuration for ng serve requests?