Server-Side Angular Interceptor for HTTP level caching based on HTTP Cache-Control headers
npm install @ngx-http-cache-control/coresh
npm i @ngx-http-cache-control/core
`
$3
This package should only be imported part of the server-side Angular module. Typically that module is placed in a file named app.server.module.ts. universal-starter can serve as a good reference point.
`ts
import { NgModule } from '@angular/core';
import {
ServerModule,
ServerTransferStateModule
} from '@angular/platform-server';
import { HttpCacheControlCoreModule } from "@ngx-http-cache-control/core";
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
HttpCacheControlCoreModule
],
bootstrap: [AppComponent]
})
export class AppServerModule { }
`
$3
#### Overriding default memory cache config
`ts
import {
MemoryCacheStoreConfig,
T_MEMORY_CACHE_STORE_CONFIG
} from "@ngx-http-cache-control/core";
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
HttpCacheControlCoreModule
],
providers: [
{
provide: T_MEMORY_CACHE_STORE_CONFIG,
useValue: {
maxCacheSizeInBytes: 500 1024 1024 // default is 100MB
} as MemoryCacheStoreConfig
}
]
bootstrap: [AppComponent]
})
export class AppServerModule { }
`
#### Overriding default cache policy options
Default values should be fine for most use-cases.
`ts
import {
CachePolicyOptions,
T_CACHE_POLICY_OPTIONS
} from "@ngx-http-cache-control/core";
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
HttpCacheControlCoreModule
],
providers: [
{
provide: T_CACHE_POLICY_OPTIONS,
useValue: {
trustServerDate: false
} as CachePolicyOptions
}
]
bootstrap: [AppComponent]
})
export class AppServerModule { }
`
#### Replacing the cache layer
By default the cache is an in-memory LRU cache.
This is a really simple and fast solution (no external services are required, works out of the box).
The main disadvantage: cache will not be shared if you have multiple node processes (each process will have its own memory cache). Depending on your use-case, this might be completely fine or totally unacceptable.
You can easily override the default cache store with your own implementation (there is a plan to officially support Redis later on).
##### First create a service that implements the CacheStore interface
`ts
import { Injectable } from "@angular/core";
import { CacheStore } from "@ngx-http-cache-control/core"
@Injectable()
export class MyCacheStore implements CacheStore {
/**
* Return the data cached behind this key.
* Don't forget to deserialize if you stored the data serialized
*/
get(key: string): Promise {
// implementation comes here
}
/**
* Store the data cached under the given key.
*
* item can be an object and need to be serialized
* if you use a store that only accepts string values.
*
* Using maxAge is optional.
* It can help keeping the cache size smaller.
* If you don't use it and store the data forever,
* it will not cause any problems. Library validates max age
* based on the headers and won't use cached response if it expired.
*/
set(key: string, item: T, maxAge?: number): Promise {
// implementation comes here
}
}
`
##### Then provide your service as the cache store
`ts
import {
CachePolicyOptions,
T_CACHE_POLICY_OPTIONS
} from "@ngx-http-cache-control/core";
import { MyCacheStore } from "./my-cache-store";
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
HttpCacheControlCoreModule
],
providers: [
{
provide: T_CACHE_STORE,
useClass: MyCacheStore
}
]
bootstrap: [AppComponent]
})
export class AppServerModule { }
`
#### Logging
By default nothing is logged. If you'd like to see console logs for debugging purposes, you can set process.env.TRACE_NG_HTTP_CACHE env variable to a truthy value when starting the server.
`shell
process.env.TRACE_NG_HTTP_CACHE node your-server.js
`
#### Monitoring the behavior using events
If you'd like to monitor how the cache behaves (which requests are getting cached, when is a response served from cache etc.), you can subscribe to the events emitted by the http interceptor.
Events are strongly typed.
`ts
import {
HttpCacheControlInterceptor,
ReturnResponseFromCacheEvent
} from "@ngx-http-cache-control/core";
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
HttpCacheControlCoreModule
],
bootstrap: [AppComponent]
})
export class AppServerModule {
constructor(interceptor: HttpCacheControlInterceptor) {
// you can inject the interceptor anywhere in your app
interceptor.events.subscribe(event => {
if (event instanceof ReturnResponseFromCacheEvent) {
// do something
}
})
}
}
`
Why was this package created?
$3
To render a more complex page in a web application, typically the app needs to make multiple HTTP requests to get the required data for the given view.
With a traditional Single-Page Application (SPA) approach, normally the server serves an application shell and when the application is bootstrapping on the client-side, it will make these calls. When API responses arrive one by one, App will render the view progressively. Most of these requests are GET requests that are often cached by the browser (if user is a returning visitor) or by a CDN / HTTP Accelerator (i.e.: Varnish).
When Angular is running on the server, to render the requested page, all required HTTP calls are made on the server to get the data and generate the view server-side. Angular will make HTTP calls. Even if these API calls return Cache-Control headers, Angular will ignore them and for every incoming requests, Angular will make those HTTP calls all over again. If you have a simple application with a few visitors, this might be acceptable.
For a more complex application with high-traffic, making those HTTP calls and waiting for the response can easily become a bottleneck with Server-Side rendering.
`
Simple example without caching API calls:
* 5 concurrent req per second to get the same page
(it could be the traffic hitting your home page)
* for each request, app needs to make 10 API calls to render the view
(to get footer, menu, banners etc.)
That's 50 HTTP calls to the API per second.
90.000 HTTP calls each 30 minutes.
If all those API calls return Cache-Control headers
and allow caching for 30 minutes, then by using
@ngx-http-cache-control/core, it could be 10 HTTP calls per 30 minutes.
``