How to use NestJS Versioning with Router module - node.js

In our nest 8 project we use the RouterModule for routing between different parts of the app:
#Module({
imports: [
RouterModule.register([
{
path: '/internal',
module: InternalModule
},
{
path: '/external',
module: ExternalModule
}
]),
],
})
export class AppModule {}
In the InternalModule we would like to use NestJS versioning:
#Controller('someroute')
export class InternalController {
#Get(':id')
#Version(['1'])
async getPerson(#Param('id') id): Promise<any> {}
}
Routing without versioning works, versioning without routing too, but the combination does not.
I tried calling it via "localhost:port/v1/internal/someroute" and "localhost:port/internal/v1/someroute"
Versioning is enabled:
app.enableVersioning({
type: VersioningType.URI
});

Related

How to use a custom middleware with nestjs-telegraf?

I am trying to implement custom Telegraf middleware with nestjs-telegraf library and connection to DB using Prisma.
My AppModule is:
#Module({
imports: [
TelegrafModule.forRootAsync({
imports: [ConfigModule, LoggerModule],
useFactory: (configService: ConfigService, logger: LoggerMiddleware) => {
return {
token: configService.get<string>("TELEGRAM_TOKEN")!,
middlewares: [sessionMiddleware, logger.use]
};
},
inject: [ConfigService, LoggerMiddleware]
}),
PrismaModule
],
controllers: [],
providers: [...someProviders]
})
export class AppModule {}
LoggerMiddleware:
#Injectable()
export class LoggerMiddleware {
constructor(private readonly prisma: PrismaService) {}
async use(ctx: Context, next: NextFunction) {
const listUser = await this.prisma.user.findMany()
console.log('listUser = ', listUser)
next()
}
}
LoggerModule:
#Module({
imports: [PrismaModule],
providers: [LoggerMiddleware, PrismaService],
exports: [LoggerMiddleware]
})
export class LoggerModule {}
It starts without errors and code reaches my logger middleware but then I am getting an error:
TypeError: Cannot read properties of undefined (reading 'prisma')
I have access to Prisma service from another provider and a connection to DB works.
At the start, nest initializes all dependencies successfully but I don't understand why during execution it fell with this error. What did I do wrong?
The answer was given by Alexander Bukhalo on github

Why does it throw an error cannot read property of undefined (reading 'length') when running a GraphQL Subscription with Redis?

Overview
Working on a NestJS project with GraphQL using a laptop with Window OS
Experimenting with GraphQL Subscriptions using graphql-redis-subscription#2.5.0 package
Redis is used in a docker container, see the docker-compose.yml below
The problem arose when the subscription postAdded is executed in GraphQL Playground. Instead of hanging to listen for events, it had crashed before I performed createPost mutation.
My code (I only include some important details)
posts.resolver.ts
import { Inject, UseGuards } from '#nestjs/common';
import { Args, Context, Mutation, Resolver, Subscription } from '#nestjs/graphql';
import { RedisPubSub } from 'graphql-redis-subscriptions';
import { GraphqlJwtAuthGuard } from '../auth/guards';
import { RequestWithUser } from '../auth/interfaces';
import { PUB_SUB } from '../pubsub/pubsub.module'
const POST_ADDED_EVENT = 'postAdded';
#Resolver(() => Post)
export class PostsResolver {
constructor(
private postsService: PostsService,
#Inject(PUB_SUB) private pubSub: RedisPubSub,
) {}
// my subscription (issue)
#Subscription(() => Post)
postAdded() {
return this.pubSub.asyncIterator(POST_ADDED_EVENT);
}
// createPost method
#Mutation(() => Post)
#UseGuards(GraphqlJwtAuthGuard)
async createPost(
#Args('input') createPostInput: CreatePostInput,
#Context() context: { req: RequestWithUser },
) {
// just create a new post (assuming it works)
const newPost = await this.postsService.create(
createPostInput,
context.req.user,
);
this.pubSub.publish(POST_ADDED_EVENT, { postAdded: newPost });
return newPost;
}
}
pubsub.module.ts
import { ConfigService } from '#nestjs/config';
import { RedisPubSub } from 'graphql-redis-subscriptions';
import { Global, Module } from '#nestjs/common';
export const PUB_SUB = 'PUB_SUB';
#Global()
#Module({
providers: [
{
provide: PUB_SUB,
useFactory: (configService: ConfigService) =>
new RedisPubSub({
connection: {
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
},
}),
inject: [ConfigService],
},
],
exports: [PUB_SUB],
})
export class PubSubModule {}
app.module.ts
import { PubSubModule } from './pubsub/pubsub.module';
#Module({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
playground: true,
autoSchemaFile: path.join(process.cwd(), 'src/schema.gql'),
installSubscriptionHandlers: true,
}),
PubSubModule,
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
REDIS_HOST: Joi.string().required(),
REDIS_PORT: Joi.number().required()
}),
}),
],
providers: [AppService, AppResolver],
})
export class AppModule {}
version: '3'
services:
redis:
image: 'redis:alpine'
ports:
- '6379:6379'
redis-commander:
image: rediscommander/redis-commander:latest
environment:
- REDIS_HOSTS=local:redis:6379
ports:
- '8081:8081'
depends_on:
- redis
All the environment variables have already been defined in .env file.
REDIS_HOST="localhost"
REDIS_PORT=6379
When I run yarn start:dev and execute the subscription in GraphQL Playground
subscription {
postAdded {
id
title
paragraphs
}
}
it raises an error like this:
{
"errors": [
{
"message": "Cannot read properties of undefined (reading 'length')",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"postAdded"
]
}
]
}
The terminal that monitors NestJS also raises an error like this:
[Nest] 8080 - 07/21/2022, 9:30:24 AM ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'length')
TypeError: Cannot read properties of undefined (reading 'length')
at JavascriptRedisParser.execute (C:\Users\HP\nestjs-project\node_modules\redis-parser\lib\parser.js:530:38)
at Object.data (C:\Users\HP\nestjs-project\node_modules\ioredis\built\DataHandler.js:25:20)
at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:207:39)
at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
at ClassTransformer.instanceToPlain (C:\Users\HP\nestjs-project\node_modules\src\ClassTransformer.ts:25:21)
at Object.classToPlain (C:\Users\HP\nestjs-project\node_modules\src\index.ts:23:27)
I have installed all the necessary dependencies like ioredis, graphql-redis-subscriptions and even graphql-subscriptions but the errors still exist. Redis also seems to be running properly.
I have tried reading the error logs but it did not occur in my source code and doing some research on StackOverFlow but none seems to have solved the problem.
Are you by any chance using a global ClassSerializerInterceptor?? Because I was running into the same problem just today and I solved by removing the interceptor. It happened because the subscription needs to receive an instance of AsyncIterable but the class serializer turns it into a plain object.
Apart from that I would recommend you change the GraphQl config, remove the installSubscriptionHandlers and change the config like this:
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
playground: true,
autoSchemaFile: path.join(process.cwd(), 'src/schema.gql'),
// remove this option:
// installSubscriptionHandlers: true,
// add the following:
subscriptions: {
"graphql-ws": true // or config object
}
}),
You can read more about it in the nestjs docs
I hope this solves your problem.

Nestjs middleware with dependencies

I am having trouble creating a middleware that has two dependencies (TypeORModule.forFeature([USER]), FirebaseModule).
What I have done is create an AuthModule which looks like this:
#Module({
imports: [
FirebaseModule,
TypeOrmModule.forFeature([User])
],
providers: [
AuthMiddleware
],
})
and the middleware which looks like this
export class AuthMiddleware implements NestMiddleware {
constructor(
#InjectRepository(User)
private usersRepository: Repository<User>,
private firebaseService: FirebaseService
) {}
async use(req: Request, res: Response, next: () => void) {...}
}
and my app module which looks like this
#Module({
imports: [
TypeOrmModule.forRoot({
...config.get("database"),
entities: [__dirname + '/entities/**/*.{js,ts}']
}),
AuthModule,
exampleModule
],
providers: [
AuthMiddleware
]
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer.apply(AuthMiddleware).forRoutes("*")
}
}
I get many errors and I try to shift things around to make it work but I simply can't get it to happen. I get errors from
Please make sure that the argument UserRepository at index [0] is available in the module(sometimes AppModule, sometimes exampleModule) context.
Do other modules (controller ones, as in providing api services) need to also import the middleware module if it applies to them too?
In general, how do I go on about implementing middlewares that depend on external modules? Do they have to be modules so I can import the requires modules?
I'd love some help, thanks!
You shiouldn't need to re-add AuthMiddleware to the AppModule's providers. It already exists in AuthModule. Also, you can bind the middleware inside the AuthModule if you want instead of in the AppModule and it will have the same global scope.

Nestjs - connect bull-board in a normal controller

I created an app using nest.js and bull.
I added bull-board package to monitor my queues, but in documentation, only one way to add it to the app is mount as middleware:
In main.ts:
app.use('/admin/queues', bullUI);
Is there any way to add bullUI in a normal nest controller, after jwt auth? Like:
#UseGuards(JwtAuthGuard)
#Get("queues")
activate() {
return UI
}
You can use any express middleware like this inside controllers, but maybe some cases cause errors like serving static files with Guard exception and etc.
#UseGuards(JwtAuthGuard)
#Get("queues/*")
activate(#Req() req, #Res() res) {
bullUI(req, res)
}
I've got this working via a middleware consumer, so something like this:
import { router } from 'bull-board';
#Module({
imports: [
NestBullModule.forRoot({ redis }),
],
providers: [],
})
export class BullModule {
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(router)
.forRoutes('/admin/queues');
}
}
I'd like to extend the original answer of #JCF, mainly, because it's working and much easier to understand.
I am using not default bull, with #nestjs/queue, but an improved version of BullMQ from anchan828 repo, with NestJS decorators, but I guess in both cases, the result will be the same.
The queue.module file:
#Module({
imports: [
BullModule.forRoot({
options: {
connection: {
host: redisConfig.host,
port: redisConfig.port,
},
},
}),
/** DI all your queues and Redis connection */
BullModule.registerQueue('yourQueueName'),
],
controllers: [],
providers: [],
})
export class QueueModule {
constructor (
#BullQueueInject('yourQueueName')
private readonly queueOne: Queue,
) {
/** Add queues with adapter, one-by-one */
setQueues([new BullMQAdapter(this.queueOne, { readOnlyMode: false })])
}
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(router)
.forRoutes('/admin/queues');
}
}
Then just add it, to parent AppModule, via import, like that:
I am not sure, that Redis connection is needed here, for parent AppModule
#Module({
imports: [
BullModule.forRoot({
options: {
connection: {
host: redisConfig.host,
port: redisConfig.port,
},
},
}),
QueueModule
],
controllers: [],
providers: [],
})
export class AppModule {}
run the main.js, and visit, localhost:8000/admin/queues

Caching return value from a service method

I am using nestjs and have just installed the cache-manager module and are trying to cache a response from a service call.
I register the cache module in a sample module (sample.module.ts):
import { CacheInterceptor, CacheModule, Module } from '#nestjs/common';
import { SampleService } from './sample.service';
import { APP_INTERCEPTOR } from '#nestjs/core';
import * as redisStore from 'cache-manager-redis-store';
#Module({
imports: [
CacheModule.register({
ttl: 10,
store: redisStore,
host: 'localhost',
port: 6379,
}),
],
providers: [
SampleService,
{
provide: APP_INTERCEPTOR,
useClass: CacheInterceptor,
}
],
exports: [SampleService],
})
export class SampleModule {}
Then in my service (sample.service.ts):
#Injectable()
export class SampleService {
#UseInterceptors(CacheInterceptor)
#CacheKey('findAll')
async findAll() {
// Make external API call
}
}
Looking at redis I can see that nothing is cached for the service method call. If I use the same approach with a controller, then everything works fine and I can see the cached entry in my redis database. I am thinking that there is no way out of the box to cache individual service method calls in nestjs.
Reading the documentation it seems that I am only able to use this approach for controllers, microservices and websockets, but not ordinary services?
Correct, it is not possible to use the cache the same way for services as for controllers.
This is because the magic happens in the CacheInterceptor and Interceptors can only be used in Controllers.
However, you can inject the cacheManager into your service and use it directly:
export class SampleService {
constructor(#Inject(CACHE_MANAGER) protected readonly cacheManager) {}
findAll() {
const value = await this.cacheManager.get(key)
if (value) {
return value
}
const respone = // ...
this.cacheManager.set(key, response, ttl)
return response
}

Resources