I have a simple nestjs application, where I have set up a CacheModule using Redis store as follows:
import * as redisStore from 'cache-manager-redis-store';
CacheModule.register({
store: redisStore,
host: 'redis',
port: 6379,
}),
I would like to use it to store a single value, however, I do not want to do it the built-in way by attaching an interceptor to a controller method, but instead I want to control it manually and be able to set and retrieve the value in the code.
How would I go about doing that and would I even use cache manager for that?
You can use the official way from Nest.js:
1. Create your RedisCacheModule:
1.1. redisCache.module.ts:
import { Module, CacheModule } from '#nestjs/common';
import { ConfigModule, ConfigService } from '#nestjs/config';
import * as redisStore from 'cache-manager-redis-store';
import { RedisCacheService } from './redisCache.service';
#Module({
imports: [
CacheModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
store: redisStore,
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
ttl: configService.get('CACHE_TTL'),
}),
}),
],
providers: [RedisCacheService],
exports: [RedisCacheService] // This is IMPORTANT, you need to export RedisCacheService here so that other modules can use it
})
export class RedisCacheModule {}
1.2. redisCache.service.ts:
import { Injectable, Inject, CACHE_MANAGER } from '#nestjs/common';
import { Cache } from 'cache-manager';
#Injectable()
export class RedisCacheService {
constructor(
#Inject(CACHE_MANAGER) private readonly cache: Cache,
) {}
async get(key) {
await this.cache.get(key);
}
async set(key, value) {
await this.cache.set(key, value);
}
}
2. Inject RedisCacheModule wherever you need it:
Let's just assume we will use it in module DailyReportModule:
2.1. dailyReport.module.ts:
import { Module } from '#nestjs/common';
import { RedisCacheModule } from '../cache/redisCache.module';
import { DailyReportService } from './dailyReport.service';
#Module({
imports: [RedisCacheModule],
providers: [DailyReportService],
})
export class DailyReportModule {}
2.2. dailyReport.service.ts
We will use the redisCacheService here:
import { Injectable, Logger } from '#nestjs/common';
import { Cron } from '#nestjs/schedule';
import { RedisCacheService } from '../cache/redisCache.service';
#Injectable()
export class DailyReportService {
private readonly logger = new Logger(DailyReportService.name);
constructor(
private readonly redisCacheService: RedisCacheService, // REMEMBER TO INJECT THIS
) {}
#Cron('0 1 0 * * *') // Run cron job at 00:01:00 everyday
async handleCacheDailyReport() {
this.logger.debug('Handle cache to Redis');
}
}
You can check my sample code here.
Building on Ahmad's comment above, I used the following to enable redis in my nestjs application:
Install and setup nestjs-redis https://www.npmjs.com/package/nestjs-redis per docs.
See the docs here on how to write and read values in a Redis store:
https://github.com/NodeRedis/node-redis
If you're connection a external Redis, I recommend to use 'async-redis' package.
The code will be:
import * as redis from 'async-redis';
import redisConfig from '../../config/redis';
On redisConfig:
export default {
host: 'your Host',
port: parseInt('Your Port Conection'),
// Put the first value in hours
// Time to expire a data on redis
expire: 1 * 60 * 60,
auth_pass: 'password',
};
So, you run:
var dbConnection = redis.createClient(config.db.port, config.db.host,
{no_ready_check: true});
Now you can, execute commands like set and get for your Redis Database.
Related
I am writing a NestJS application. Now I want to install the Express middleware express-openapi-validator.
However, I can't get it to work. There is a description for how to install the express-openapi-validator in express, but it always results in errors.
For example
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(middleware({apiSpec "./bff-api.yaml"}))
.forRoutes(OrganizationController)
}
}
results in
error TS2345: Argument of type 'OpenApiRequestHandler[]' is not assignable to parameter of type 'Function | Type<any>'.
Type 'OpenApiRequestHandler[]' is missing the following properties from type 'Type<any>': apply, call, bind, prototype, and 4 more.
How can I install this middleware in NestJS?
I added a NestJS example to express-openapi-validator (static link for posterity).
The AppModule looks basically identical, although you don't need to iterate over the middlewares:
#Module({
imports: [PingModule],
providers: [{ provide: APP_FILTER, useClass: OpenApiExceptionFilter }],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
...OpenApiValidator.middleware({
apiSpec: join(__dirname, './api.yaml'),
}),
)
.forRoutes('*');
}
}
I also added an exception filter to convert the error from express-openapi-validator to a proper response; otherwise I would always get a 500 error. You could also use this approach to convert the error into a custom error format.
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
import { Response } from 'express';
import { error } from 'express-openapi-validator';
#Catch(...Object.values(error))
export class OpenApiExceptionFilter implements ExceptionFilter {
catch(error: ValidationError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
response.status(error.status).json(error);
}
}
interface ValidationError {
status: number;
message: string;
errors: Array<{
path: string;
message: string;
error_code?: string;
}>;
path?: string;
name: string;
}
I have now got it working:
configure(consumer: MiddlewareConsumer) {
middleware({
apiSpec: `${__dirname}/../api-doc/bff-api.yaml`
}).forEach(value => consumer.apply(value).forRoutes(OrganizationController))
}
I use angular-auth-oidc-client to implement authentication for a web app using Angular 8,
It's working fine when storing user data in the localStorage or sessionStorage
but when using a cookies storage implementation, Errors like client:172 [WDS] Disconnected! start shown in the console, and application stop working tell i delete the stored cookies manually.
As the documentation on how to add custom storage here Custom Storage,
The cookie storage implementation i did is:
import { Injectable, Inject } from '#angular/core';
import { OidcSecurityStorage } from 'angular-auth-oidc-client';
import { CookieService } from 'ngx-cookie-service';
import { OidcConfiguration } from '../config/oidc.config';
#Injectable({
providedIn: 'root'
})
export class CookiesStorageService implements OidcSecurityStorage {
constructor(private cookieService: CookieService, #Inject('OidcConfig') private oidcConfig: OidcConfiguration) {
}
public read(key: string): any {
let val = this.cookieService.get(key + '_' + this.oidcConfig.openIdConfiguration.client_id);
if (val && val != '') {
return JSON.parse(val);
}
return;
}
public write(key: string, value: any): void {
if (value && value != '') {
value = value === undefined ? null : value;
this.cookieService.set(
key + '_' + this.oidcConfig.openIdConfiguration.client_id,
JSON.stringify(value));
}
}
}
Value for this.oidcConfig.openIdConfiguration.client_id is "OPApiClient" which to append the clientId as a key name postfix.
In the AppModule i register this custom storage as shown:
import { NgModule, Inject } from '#angular/core';
import { CommonModule } from '#angular/common';
import { AuthModule, OidcSecurityService } from 'angular-auth-oidc-client';
...
#NgModule({
declarations: [...],
imports: [
CommonModule,
AuthModule.forRoot({ storage: CookiesStorageService }),
...
],
providers: [...]
})
Is there any mistake in the cookies storage that i have or anything i missed? any help would be very thankful.
I would like to add version for my modules, but i don't know how can I do this. I tried to create a common module.ts but the same name of services killed each us. I tried different module.ts for the versions, it was better but the services with same names didn't work.
This is my last structure:
test-module
1.0
controllers
test.controller.ts
services
test.service.ts
test.module.ts
1.1
controllers
test.controller.ts
services
test.service.ts
test.module.ts
This is my test service(s) for the versions:
import * as _ from 'lodash';
import { Injectable } from '#nestjs/common';
#Injectable()
export class TestService {
public test() {
return '1.0'; // and 1.1 in 1.1 directory
}
}
This is my module.ts:
import { Module, Logger } from '#nestjs/common';
import { TestModule as DorotTwo } from 'test-module/1.1/test.module';
import { TestModule as DorotOne } from 'test-module/1.0/test.module'
#Module({
controllers: [ProtobufController],
providers: [],
imports: [
DorotTwo,
DorotOne,
],
})
export class ProjectModule {
constructor() {
Logger.log('App initialized');
}
}
This is a simple test Controller in the project who want use the modules. A tried import TestService from 1.0 or 1.1 but the test function's response is always 1.0 because that is the last element in the import.
#Controller()
export class ProtobufController {
constructor(private readonly testService: TestService) {
console.log(this.testService.test()); // Always 1.0
}
.....
It is working if I use full different names for services for example (eg: UserAuthenticationService10, RegisterAuthenticationService10), but this is horrible and if i forget rename it in new version, it will overwrite.
Is exists an example where I can read how can I create this versioned module?
Would using custom providers be a satisfying solution for you?
Example:
// 1.0
#Module({
providers: [
{ provide: 'TestService_1.0', useClass: TestService }
]
})
export class TestModule {}
// 1.1
#Module({
providers: [
{ provide: 'TestService_1.1', useClass: TestService }
]
})
export class TestModule {}
// Then
#Controller()
export class ProtobufController {
constructor(
#Inject('TestService_1.0') private readonly testService_10,
#Inject('TestService_1.1') private readonly testService_11
) {
console.log(this.testService_10.test());
console.log(this.testService_11.test());
}
}
I obviously haven't tested this and you should adapt it to your usecase. I suggest you to have a look at https://docs.nestjs.com/fundamentals/custom-providers.
I'm building a GraphQL API using Nest framework and I'm trying to implement 3rd party express middlewares (express-rate-limit and express-slow-down) into some queries and mutations.
The problem is all graphql mutations and queries use the same endpoint, so I can't explicitly tell to which query or mutations shall the middleware be applied, because you can only do that using route's path (which is the same across the API).
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '#nestjs/common'
import * as rateLimit from 'express-rate-limit'
import * as RedisStore from 'rate-limit-redis'
import { RedisClient } from 'redis'
#Module({
providers: [],
exports: [],
})
export default class SecurityModule implements NestModule
{
constructor(protected readonly redisClient: RedisClient)
{
}
configure(consumer: MiddlewareConsumer)
{
consumer.apply(
new rateLimit({
max: 300,
windowMs: 15 * 60 * 1000,
store: new RedisStore({ client: this.redisClient }),
})).forRoutes({ path: '/graphql', method: RequestMethod.ALL }) // this would apply the middleware to all queries and mutations
}
}
So I tried using both guards and interceptors for that purpose, but failed miserably.
It's a fail for an obvious reason.
The Error: Can't set headers after they are sent is thrown.
/* !!! My Interceptor would like quite identical */
import { ExecutionContext, Injectable, CanActivate } from '#nestjs/common'
import * as speedLimit from 'express-slow-down'
import { Request, Response } from 'express'
#Injectable()
export default class SpeedLimitGuard implements CanActivate
{
constructor(
protected readonly options: speedLimit.Options,
) {
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const { req, res }: { req: Request, res: Response } = context.getArgs()[2]
speedLimit({ ...this.options })(req, res, req.next)
return true
}
}
import { NestInterceptor, ExecutionContext, Injectable, INestApplication, INestExpressApplication } from '#nestjs/common'
import { Observable } from 'rxjs'
import * as speedLimit from 'express-slow-down'
// import { Request, Response } from 'express'
import { ApplicationReferenceHost } from '#nestjs/core'
import { RedisClient } from 'redis'
import * as RedisStore from 'rate-limit-redis'
#Injectable()
export default class SpeedLimitInterceptor implements NestInterceptor
{
constructor(private readonly appRefHost: ApplicationReferenceHost,
private readonly redisClient: RedisClient, )
{}
intercept<T>(context: ExecutionContext, call$: Observable<T>): Observable<T>
{
// const { req: request, res: response }: { req: Request, res: Response } = context.getArgs()[2]
const httpServer = this.appRefHost.applicationRef
const app: INestApplication & INestExpressApplication = httpServer.getInstance()
app.use(speedLimit({
delayAfter: 1,
store: new RedisStore({
prefix: 'test_',
client: this.redisClient,
}),
}))
app.use((req, res, next) => {
console.log('is middleware triggered', { req, res })
next()
})
return call$
}
}
Is there any way to apply a 3rd party express middleware to a GraphQL Mutation/Query explicitly?
So from the bottom, guards are working, because I'm the living human bean that can prove it:
#Query('getHome')
#UseGuards(GraphqlGuard)
async findOneById(#Args('id') id: string): Promise<HomeEntity> {
return await this.homeService.findOneById(id);
}
and it's just working.
This is GraphqlGuard.ts
import {ExecutionContext, Injectable} from '#nestjs/common';
import {GqlExecutionContext} from '#nestjs/graphql';
import {AuthGuard} from '#nestjs/passport';
import {ExecutionContextHost} from '#nestjs/core/helpers/execution-context.host';
import {Observable} from 'rxjs';
#Injectable()
export class GraphqlGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const ctx = GqlExecutionContext.create(context);
const {req} = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
}
But to live with context, you have to make it works for you, so, wherever you're passing graphql config, there is an context callback, and for me it looks like this:
context: (context) => {
let req = context.req;
if (context.connection) {
req = context.connection.context.req;
}
return {req};
}
I'm checking here connection for context from websocket. Im using global interceptors so, they're working like a charm. But you still can use #UseInterceptors(SomeInterceptor) decorator and it also works. And btw Middlewares, at the end, I doesn't need any of them guards, pipes, validators and interceptors for me was quite enough.
Regards.
I am using code like this to extend RouterOutlet and create app wide authentication and route protection
import {Directive, Attribute, ViewContainerRef, DynamicComponentLoader} from '#angular/core';
import {Router, ComponentInstruction} from '#angular/router';
import {Router} from '#angular/router';
import {RouterOutletMap} from '#angular/router/src/router_outlet_map';
import {RouterOutlet} from '#angular/router/src/directives/router_outlet';
import {Authentication} from '../common/authentication.service';
#Directive({
selector: 'router-outlet'
})
export class LoggedInRouterOutlet extends RouterOutlet {
publicRoutes:any;
isAuthenticated:boolean;
//private router: any;
constructor(public _elementRef: ElementRef, public _loader: DynamicComponentLoader,
public _parentRouter: Router, #Attribute('name') nameAttr: string, public authService:Authentication) {
super(_elementRef, _loader, _parentRouter, nameAttr);
this.isAuthenticated = authService.isLoggedIn();
//this.router = _parentRouter;
/**
* DEFINE PUBLIC ROUTES
*
* The Boolean following each route below denotes whether the route requires authentication to view.
*
* Format: key/value pair
* - key is the /route url "/login", "/signup", etc
* - value is a boolean true/false
* `true` means it's a publicly available route. No authentication required
* `false` means it's a protected route which is hidden until user is authenticated
*
*/
this.publicRoutes = {
'login': true,
'signup': true,
'404': true
};
} // end constructor
routeIsActive(routePath:string) {
return this.router.url == routePath;
}
activate(instruction: ComponentInstruction) {
// let url = instruction.urlPath;
let url = this.router.url;
// If the url doesn't match publicRoutes and they are not authenticated...
if (!this.publicRoutes[url] && !this.isAuthenticated) {
// todo: redirect to Login, may be there a better way?
this.router.navigateByUrl('/login');
}
return super.activate(instruction);
}
}
Problem is that ComponentInstruction does not exist in the new v3.0.0-alpha8 router, and the super method signature has changed. How do I update this to work in the new router? I cannot find any documentation explaining the changes.
ComponentInstruction has been deprecated. In the current RC4 version of Angular2, this class has been listed under reouter-deprecated. With RC5 coming in, this package would be dropped.
RouterOutlet has changed a lot over time and to make your class LoggedInRouterOultet work, you have to use CanActivate interface.
You can do something like this:
Have an injectable service like LoggedInActivator shown here:
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { LogInService } from './login.service';
#Injectable()
export class LoggedInActivator implements CanActivate {
constructor(private loginService: LogInService) {}
canActivate() {
return this.loginService.isLoggedIn();
}
}
Add canActivate and map it to LoggedInActivator on component while defining route:
{ path: 'home', component: HomeComponent, canActivate: [LoggedInActivator] }
I hope this helps!
because in new router, it uses CanActivate