Use of multiple back-ends, with different headers, in Angular applications could be boring and look a mess, but what can we do?
Instead of making imports and references to the environment file always when necessary, I usually use HttpInterptor to add the correct back-end base URL and set headers based on the context. If you are having problems in treat different environment in server-render, it also could be the solution.
Basic, the idea is use a string prefix in the URL as “@api-x” to identify the back-end base url on each HTTP request.
this.http.get('@api-x/specific_endpoint')
How do it work? Follow the guide bellow.
1- First, you need to create a Interceptor using HttpInterceptor:
import { Injectable } from ‘@angular/core’;
import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent } from ‘@angular/common/http’;
import { Observable } from ‘rxjs’;
import { environment } from ‘environments/environment’;
@Injectable()
export class ApiInterceptor implements HttpInterceptor {
constructor() { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// Get the request url
let requestUrl = req.url;
// if the request URL have the string prefix,
// then make the replace by the correct url
if (requestUrl.indexOf(‘@api-x’) !== -1) {
requestUrl = requestUrl.replace(‘@api-x’, environment.api);
}
// clone the http request
req = req.clone({
url: requestUrl
});
// move to next HttpClient request life cycle
return next.handle(req);
}
}
Basic here, we’re creating a interceptor that receive the request and replace the string prefix “api-x” in the requested URL by the correct URL defined in the environment file.
2- Add the created interceptor in some application module: register in providers array.
...
imports: [
// import the http client module
HttpClientModule,
...
],
providers: [
// register the interceptor created
{
provide: HTTP_INTERCEPTORS,
useClass: ApiInterceptor,
multi: true
},
…
]
...
I recommend register it in AppModule or SharedModule, if the application have the SharedModule. But attention, we need to provide at same module that the HttpClientModule are being imported or in a child module. Why do you need it? In resume it is because how the dependency injection works in Angular.
3- Import the http client and create the request: done.
...
constructor(
// import the dependency with the constructor
private http: HttpClient
) {}
getNews() {
return this.http.get(‘@api-x/specific_endpoint_to_get_news');
}
...
Now you can make the request without difficulty just using the prefix @api-x in the request URL parameter.
But if we need more base backed URLs, what we can do?
4- Add more prefix.
Modify the implementation of ApiInterceptor.
...
let requestUrl = req.url;
if (requestUrl.indexOf(‘@api-x’) !== -1) {
requestUrl = requestUrl.replace(‘@api-x’, environment.api);
}
//added: a new prefix "@api-y"
if (requestUrl.indexOf(‘@api-y’) !== -1) {
requestUrl = requestUrl.replace(‘@api-y’, environment.otherBackEnd);
}
...
Add the new request to new api.
...
constructor(
private http: HttpClient
) {}
getNews() {
return this.http.get('@api-x/specific_endpoint_to_get_news');
}
getDocuments() {
return this.http.get('@api-y/specific_endpoint_to_get_documents');
}
...
Ok, now we can use multiple APIs in a easy way, but if we need to set headers, what could we do?
5- Set headers based on the context.
Modify the ApiInterceptor to accept it:
...
// added: capture the token - could be a service call here
const tokenApiX = localStorage.getItem(‘tokenApiX’);
let requestUrl = req.url;
if (requestUrl.indexOf(‘@api-x’) !== -1) {
requestUrl = requestUrl.replace(‘@api-x’, environment.api);
// added: add the header
// only to request with url that contain the prefix @api-x
req = req.clone({setHeaders: {
Content-Type: 'application/json',
Access-Control-Allow-Origin: '*',
Authorization: "Bearer " + tokenApiX
}});
}
if (requestUrl.indexOf(‘@api-y’) !== -1) {
requestUrl = requestUrl.replace(‘@api-y’, environment.otherBackEnd);
}
...
How we have the control of the workflow for each back-end base url, we can just apply the headers that the application need just to a specific string prefix or to both. We can do everything what we want. It is powerful.
Ok, but how it could help us in server render context?
6- Handle different back-end base URLs depending of runtime environment: browser or node (server).
Modify the interceptor.
import { PLATFORM_ID, Inject } from ‘@angular/core’;
import { isPlatformBrowser} from ‘@angular/common’;
…
export class ApiInterceptor implements HttpInterceptor {
constructor(@Inject(PLATFORM_ID) platformId: string) { }
intercept(
…
if (requestUrl.indexOf(‘@api-x’) !== -1) {
// added: verification of browser context
if (isPlatformBrowser(platformId);) {
//this is only executed on the browser
requestUrl = requestUrl.replace(‘@api-x’, environment.apiExternal);
} else {
//this is only executed on the server
requestUrl = requestUrl.replace(‘@api-x’, environment.apiInternal);
}
}
…
Now we can use, with same code of http request, diferents APIS address based on the runtime context, without a lot of modification.
We also could improve the API interceptor implementation code, make a refactoring, but this is subject to another article.
Top comments (3)
Hey Leonardo,
Thanks for the tutorial. Just want to point out that you use 'urlValue' instead of 'requestUrl' in some of the code samples.
Thank you for the feedback, I fixed it.
Very beautiful indeed. Thank you, Leo!