Skip to main content

Interceptors

Interceptors wrap every request with cross-cutting logic — authentication, retry, logging, or correlation IDs — without modifying individual call sites. They run in the order they are registered and can inspect or modify both the outgoing request and the incoming response stream.


@opra/client interceptors

Register interceptors in FetchBackend.Options when constructing the client.

Class-based

Implement HttpInterceptor and pass an instance:

import { HttpInterceptor, HttpHandler, HttpBackend, OpraHttpClient } from '@opra/client';
import { Observable } from 'rxjs';

class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpBackend.RequestInit, next: HttpHandler): Observable<HttpEvent> {
req.headers.set('Authorization', `Bearer ${getAccessToken()}`);
return next.handle(req);
}
}

const client = new OpraHttpClient('https://api.example.com', {
interceptors: [new AuthInterceptor()],
});

Functional

Use HttpInterceptorFn for stateless interceptors:

import { HttpInterceptorFn, OpraHttpClient } from '@opra/client';

const loggingInterceptor: HttpInterceptorFn = (req, next) => {
console.log('→', req.method, req.url);
return next(req);
};

const client = new OpraHttpClient('https://api.example.com', {
interceptors: [loggingInterceptor],
});

@opra/angular interceptors

In Angular applications, you have two options: OPRA-level interceptors (registered on the client) and Angular-level interceptors (registered as HTTP_INTERCEPTORS). Both work — choose based on scope.

OPRA-level (client-specific)

Registered on OpraClientModule and applied only to OPRA requests:

OpraClientModule.registerClient({
serviceUrl: 'https://api.example.com',
token: API_CLIENT,
interceptors: [new AuthInterceptor()],
})

Angular-level (all HTTP requests)

Registered as Angular HTTP_INTERCEPTORS and applied to every HttpClient request in the app, including OPRA requests:

providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
]

Use Angular-level interceptors when the logic should apply globally (e.g. attaching auth tokens, handling 401 redirects). Use OPRA-level interceptors when the logic is specific to a particular API client.


Common patterns

Auth token

class AuthInterceptor implements HttpInterceptor {
constructor(private readonly getToken: () => string) {}

intercept(req: HttpBackend.RequestInit, next: HttpHandler): Observable<HttpEvent> {
const token = this.getToken();
if (token) req.headers.set('Authorization', `Bearer ${token}`);
return next.handle(req);
}
}

Retry on transient errors

import { retry } from 'rxjs';

const retryInterceptor: HttpInterceptorFn = (req, next) =>
next(req).pipe(retry({ count: 2, delay: 300 }));

Request logging

import { tap } from 'rxjs';
import { HttpEventType } from '@opra/client';

const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const start = Date.now();
return next(req).pipe(
tap(event => {
if (event.type === HttpEventType.Response) {
console.log(`${req.method} ${req.url}${event.response.status} (${Date.now() - start}ms)`);
}
}),
);
};

Correlation ID

const correlationInterceptor: HttpInterceptorFn = (req, next) => {
req.headers.set('X-Correlation-Id', crypto.randomUUID());
return next(req);
};