How can I pass REDIS_URI for NestJS cache manager? - heroku

In the official documentation this is the correct way to use the cache manager with Redis:
import * as redisStore from 'cache-manager-redis-store';
import { CacheModule, Module } from '#nestjs/common';
import { AppController } from './app.controller';
#Module({
imports: [
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
controllers: [AppController],
})
export class AppModule {}
Source: https://docs.nestjs.com/techniques/caching#different-stores
However, I did not find any documentation on how to pass Redis instance data using REDIS_URI. I need to use it with Heroku and I believe this is a common use case.

EDIT:
now they are type-safe: https://github.com/nestjs/nest/pull/8592
I've exploring a bit about how the redis client is instantiated. Due to this line I think that the options that you've passed to CacheModule.register will be forwarded to Redis#createClient (from redis package). Therefore, you can pass the URI like:
CacheModule.register({
store: redisStore,
url: 'redis://localhost:6379'
})
try this and let me know if it works.
edit:
Explaining how I got that:
Taking { store: redisStore, url: '...' } as options.
Here in CacheModule.register I found that your options will live under CACHE_MODULE_OPTIONS token (as a Nest provider)
Then I search for places in where this token will be used. Then I found here that those options were passed to cacheManager.caching. Where cacheManager is the module cache-manager
Looking into to the cacheManager.caching's code here, you'll see that your options is now their args parameter
Since options.store (redisStore) is the module exported by cache-manager-redis-store package, args.store.create method is the same function as in redisStore.create
Thus args.store.create(args) is the same as doing redisStore.create(options) which, in the end, will call Redis.createClient passing this options

Related

Global Guards in themultiple GraphQL modules with NestJS and separate access to queries and mutations in resolvers

I want to build a NestJS project where I will separate queries and mutations into two groups: Internal and Public. In the Internal GraphQL module, I will define path and resolvers without any restrictions, but for the Public, I want to define a GraphQL module with path and JWT Guard, which will look at the same resolvers but just specific mutations and queries.
I tried to do the following:
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useClass: InternalGraphQLConfig,
}),
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useClass: PublicGraphQLConfig,
}),
For protecting the public endpoint (the path is defined in the PublicGraphQLConfig by GqlOptionsFactory) I added middleware by NestMiddleware where I am checking req.originalUrl - the path from the PublicGraphQLConfig. If the URL is the public one, I am checking for the JWT otherwise is a free - internal URL.
But, I do not know how and where I can define the list of queries and mutations for the Public GraphQL model because I do not want to expose all of them.
As I can see in the documentation, this approach may be unavailable, and it is impossible to do it correctly like this. Maybe I have to use directives or something else, but I firmly believe someone has a similar/same challenge and will share an idea/solution with me.
Edit 1:
Here I will add more details about resolvers and GraphQL Module configuration.
One of my resolvers looks like the following:
...
#Resolver(DogEntity)
class DogResolver {
#Mutation(...)
async firstMutation(...) {
//
}
#Mutation(...)
async secondMutation(...) {
//
}
#Query(...)
async firstQuery(...) {
//
}
#Query(...)
async secondQuery(...) {
//
}
...
}
GraphQL Module configuration looks like this:
...
#Injectable()
export class PublicGraphQLConfig implements GqlOptionsFactory {
createGqlOptions(): Promise<ApolloDriverConfig> | ApolloDriverConfig {
return {
...
resolvers: { DogResolver, ...}
path: '/my/public/route/graphql'
};
}
}
The first: It would be amazing if I could add a "global" guardian on the GrapQL Module level with, for example guards parameter in the PublicGraphQLConfig. Because it is impossible, and adding any JWT validation in the context parameter no make sense, I have to add Middleware where I'm checking the path parameter from the GraphQL Module configuration.
The Middleware looks like this:
...
#Injectable()
export class RequestResponseLoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// For public endpoint, all resolvers required JWT token with Admin flag
if (req.originalUrl === '/my/public/route/graphql') {
this.validateJWT(req); // Do "throw exception inside"
}
...
The second: It would be amazing to add specific Mutation and/or Query in the GraphQL Module configuration. With the resolvers parameter, I can add only complete resolvers, but not specific queries or mutations. With this, I will be able to access the specific queries and mutations from different Endpoints with/out authorization requests.
The field in the GrapQL Module configuration like the following will be amazing (but, as I can see, it does not exist)
...
return {
...
resolvers: {
DogResolver:firstMutation(),
DogResolver:firstQuery(),
...
},
path: '/my/public/route/graphql'
};
...

inject nestjs service to build context for graphql gateway server

In app.module.ts I have the following:
#Module({
imports: [
...,
GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
server: {
context: getContext,
},
driver: ApolloGatewayDriver,
gateway: {
buildService: ({ name, url }) => {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }: any) {
...
},
});
},
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'iam', url: API_URL_IAM },
],
})
},
}),
]
...
})
here getContext is just a regular function which is not part of nestjs context (doesn't have injection, module capability) like below:
export const getContext = async ({ req }) => {
return {}
}
Is there any way to use nestjs services instead of plain old functional approach to build the context for graphql gateway in nestjs?
Thanks in advance for any kind of help.
I believe you're looking to create a service that is #Injectable and you can use that injectable service via a provider. What a provider will do is satisfy any dependency injection necessary.
In your scenario, I would import other modules as necessary. For building context, I would create a config file to create from env variables. Then create a custom provider that reads from the env variables and provides that implementation of the class/service to the other classes as their dependency injection.
For example, if I have a graphQL module. I would import the independent module. Then, I would provide in the providers section, the handler/service classes and the dependencies as an #injectable. Once your service class is created based on your config (which your provider class would handle), you would attach that service class to your GraphQL class to maybe lets say direct the URL to your dev/prod envs.

Nestjs displays error during production that: "Persisted queries are enabled and are using an unbounded cache."

Nestjs application does not display the error message during development only during production. I found no module where the apollo -server can be configured to cache: "bounded". The Nestjs documentation itself makes no mention of it anywhere.
The complete error message says:
Persisted queries are enabled and are using an unbounded cache. Your server is vulnerable to denial of service attacks via memory exhaustion. Set cache: "bounded" or persistedQueries: false in your ApolloServer constructor, or see https://go.apollo.dev/s/cache-backends for other alternatives.
Here are some dependencies I suspect could be related to it.
"#nestjs/apollo": "10.0.19",
"#nestjs/common": "9.0.5",
"#nestjs/core": "9.0.5",
"#nestjs/graphql": "10.0.20",
A similar issue was opened at github and sadly it was closed without any solution.
This is related to the Apollo module's configuration, by default the cache is "unbounded" and not safe for production (see : https://www.apollographql.com/docs/apollo-server/performance/cache-backends/#ensuring-a-bounded-cache)
You can easily follow their recommendation by adding the optional "cache" configuration inside your GraphQL module.
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
cache: 'bounded', // <--- This option
...
}
Or provide an external caching with KeyvAdapter see: https://github.com/apollographql/apollo-utils/tree/main/packages/keyvAdapter#keyvadapter-class
In the app.module.ts inside the import array,
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useClass: GqlConfigService,
}),
Then in the GqlConfigService I add cache: 'bounded' in the ApolloDriverConfig options.
import { ConfigService } from '#nestjs/config';
import { ApolloDriverConfig } from '#nestjs/apollo';
import { Injectable } from '#nestjs/common';
import { GqlOptionsFactory } from '#nestjs/graphql';
import { GraphqlConfig } from './config.interface';
#Injectable()
export class GqlConfigService implements GqlOptionsFactory {
constructor(private configService: ConfigService) {}
createGqlOptions(): ApolloDriverConfig {
const graphqlConfig = this.configService.get<GraphqlConfig>('graphql');
return {
// schema options
cache: 'bounded', // ! <== Added here
autoSchemaFile: graphqlConfig.schemaDestination || './src/schema.graphql',
sortSchema: graphqlConfig.sortSchema,
buildSchemaOptions: {
numberScalarMode: 'integer',
},
// subscription
installSubscriptionHandlers: true,
debug: graphqlConfig.debug,
playground: graphqlConfig.playgroundEnabled,
context: ({ req }) => ({ req }),
};
}
}

Express, Angular, Universal and how to get the base url using injection

How do I get the port number from express into Angular Universal, but still make the front end usable. (Angular 13)
So the basics of the setup. I have Angular Universal (the heros tutorial) loaded.
In the server.ts file I add this to my renderer:
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }, { provide: BASE_URL, useValue: req.get('host') }] });
The base url is defined in a new file:
export const BASE_URL = new InjectionToken<string>('BaseUrl');
Next, I need to import that base url into a component/service constructor:
#Inject(BASE_URL) private baseUrl: string
and then console log that out in my onInit or constructor...
console.log(this.baseUrl);
Run the whole thing and it gives me the number in the console where I am running my website.
Now, the front end of course crashes. (We don't know where to get the base url injection from, since we did not declare that as a provider to our app. Lets do that:
providers: [{ provide: BASE_URL, useValue: "test"}]
ok, Now... we run it, and suddenly we get "test" in our console for express, and the app works.
Is there a way to do both? Get the port from express and not bomb on the front end.
I want to get the base url when running SSR, but get an empty string when running in a browser.
The correct answer is:
Do not use a provider in app.module.ts and use the optional keyword when injecting it into your constructor/service
#Optional() #Inject(BASE_URL) private baseUrl: string

Spartacus Storefront Multisite I18n with Backend

We've run into some problems for our MultiSite Spartacus setup when doing I18n.
We'd like to have different translations for each site, so we put these on an API that can give back the messages dependent on the baseSite, eg: backend.org/baseSiteX/messages?group=common
But the Spartacus setup doesn't let us pass the baseSite? We can
pass {{lng}} and {{ns}}, but no baseSite.
See https://sap.github.io/spartacus-docs/i18n/#lazy-loading
We'd could do it by overriding i18nextInit, but I'm unsure how to achieve this.
In the documentation, it says you can use crossOrigin: true in the config, but that does not seem to work. The type-checking say it's unsupported, and it still shows uw CORS-issues
Does someone have ideas for these problems?
Currently only language {{lng}} and chunk name {{ns}} are supported as dynamic params in the i18n.backend.loadPath config.
To achieve your goal, you can implement a custom Spartacus CONFIG_INITIALIZER to will populate your i18n.backend.loadPath config based on the value from the BaseSiteService.getActive():
#Injectable({ providedIn: 'root' })
export class I18nBackendPathConfigInitializer implements ConfigInitializer {
readonly scopes = ['i18n.backend.loadPath']; // declare config key that you will resolve
readonly configFactory = () => this.resolveConfig().toPromise();
constructor(protected baseSiteService: BaseSiteService) {}
protected resolveConfig(): Observable<I18nConfig> {
return this.baseSiteService.getActive().pipe(
take(1),
map((baseSite) => ({
i18n: {
backend: {
// initialize your i18n backend path using the basesite value:
loadPath: `https://backend.org/${baseSite}/messages?lang={{lng}}&group={{ns}}`,
},
},
}))
);
}
}
and provide it in your module (i.e. in app.module):
#NgModule({
providers: [
{
provide: CONFIG_INITIALIZER,
useExisting: I18nBackendPathConfigInitializer,
multi: true,
},
],
/* ... */
})
Note: the above solution assumes the active basesite is set only once, on app start (which is the case in Spartacus by default).

Resources