Last week, I had to debug an authentication issue and came across a status code
of 0
. I’d never seen this before so I asked GPT to help me understand what was
going on.
The Setup
To reproduce the problem locally, first bootstrap a new Angular project with:
pnpm install -g @angular/cli
ng new my-app --package-manager=pnpm --defaults
cd my-app
Define an HttpInterceptor
in src/app/http-error.interceptor.ts
:
import { HttpInterceptorFn } from "@angular/common/http";
import { catchError, throwError } from "rxjs";
export const httpErrorInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError((error) => {
console.log("HTTP Error Status:", error.status);
return throwError(() => error);
})
);
};
Inject the interceptor in src/app/app.config.ts
:
import { ApplicationConfig, provideZoneChangeDetection } from "@angular/core";
import { provideRouter } from "@angular/router";
import { provideHttpClient, withInterceptors } from "@angular/common/http";
import { routes } from "./app.routes";
import { httpErrorInterceptor } from "./http-error.interceptor";
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(withInterceptors([httpErrorInterceptor])),
],
};
Add hitBackend()
method in src/app/app.component.ts
:
import { HttpClient } from "@angular/common/http";
import { Component } from "@angular/core";
import { RouterOutlet } from "@angular/router";
@Component({
selector: "app-root",
imports: [RouterOutlet],
templateUrl: "./app.component.html",
styleUrl: "./app.component.css",
})
export class AppComponent {
title = "my-app";
constructor(private http: HttpClient) {}
hitBackend() {
this.http.get("http://localhost:3000").subscribe({
next: (res) => console.log("Success", res),
error: (err) => console.log("Fail", err),
});
}
}
Create a button to call it somewhere in src/app/app.component.html
:
<button (click)="hitBackend()">Hit Backend (Triggers CORS Error)</button>
Lastly, run the frontend with:
ng serve
For backend, create server.mjs
:
import { createServer } from "http";
const server = createServer((_req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("This will not be received by a browser on a different origin.");
});
server.listen(3000, () => {
console.log("Server running at http://localhost:3000/");
});
And run the backend with:
node server.mjs
When I click the button, I see HTTP Error Status: 0
in browser console.
What I learned
Apparently, Angular’s HttpClient
makes requests using XMLHttpRequest
(XHR),
which predates fetch()
and can return 0
on fail. This was confusing on three
levels:
status
can also return normal HTTP response codes (e.g., 200, 404), so0
feels out of place.0
is usually falsy in JavaScript/TypeScript, soif (status)
can skip valid cases.0
usually means “success” in Unix, which adds to the confusion.
Regardless, the status code of 0
in XHR implies the browser never hit the
backend to receive any response. Scenarios where this could happen are:
- Network failure
- CORS error
- Request blocked or aborted
- Timeout
TL;DR
When using XHR, as in the case of Angular’s HttpClient
, a status
of 0
usually means the request never reached the server (e.g., due to CORS, network
issues, or timeouts).
In fetch
, network failures (like CORS or DNS errors) result in a TypeError
,
not a response with status: 0
.