Looking at Netsjs documentation I can see that the general approach is controller level caching utilizing CacheInterceptor,
What I am looking to achieve is Service/DB level caching - use case is mainly for Static DB data that is required by other services, Is there a way to extend the supplied Cache Module to be used from within services ?
I was also looking for a way to do this.
For now I made it work based on some research by injecting the cacheManager instance in the service, and using it directly.
import { CACHE_MANAGER, Inject, Injectable } from '#nestjs/common';
#Injectable()
export class MyService {
constructor(
#Inject(CACHE_MANAGER) protected readonly cacheManager
) {}
public async myMethod() {
const cachedData = await this.cacheManager.get(cacheKey);
if (cachedData) return cachedData;
const data = ''; // Do normal stuff here
this.cacheManager.set(cacheKey, data);
return data;
}
}
But I'm still looking for a way to create a caching decorator that would contain the cache logic, and would use the function arguments as cache key
This is how I do it (some hook):
import {CACHE_MANAGER, CacheModule, Inject, Module} from '#nestjs/common';
import {Cache} from 'cache-manager';
let cache: Cache;
#Module({
imports: [CacheModule.register({ttl: 60 * 60})]
})
export class CacheableModule {
constructor(#Inject(CACHE_MANAGER) private cacheManager: Cache) {
cache = cacheManager;
}
}
export const Cacheable = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const cacheKey = originalMethod.name + "_" + args.join('_')
const cachedData = await cache.get(cacheKey);
if (cachedData) {
originalMethod.apply(this, args).then((data: any) => {
cache.set(cacheKey, data);
})
return cachedData;
}
const result = await originalMethod.apply(this, args);
cache.set(cacheKey, result);
return result;
};
};
and use it
#Cacheable
async getProducts(projectName: string): Promise<{ [id: string]: Product }> {}
You can use one of the cache stores listed here: https://github.com/BryanDonovan/node-cache-manager#store-engines
Then you simply register that store in the cache module (https://docs.nestjs.com/techniques/caching#different-stores):
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 ApplicationModule {}
Related
New to NestJS and TypeORM, and the similar questions on SO didn't solve my problem.
I have a custom TypeORM repository in NestJS using it in service, but it fails with error:
TypeError: this.tenantRepository.createTenant is not a function.
tenants.module.ts:
import { TenantRepository } from './tenant.repository';
#Module({
imports: [
TypeOrmModule.forFeature([TenantRepository]),
],
controllers: [TenantsController],
providers: [TenantsService],
})
export class TenantsModule { }
tenant.repository.ts:
// ...
import { TenantEntity } from './entities/tenant.entity';
#EntityRepository(TenantEntity)
export class TenantRepository extends Repository<TenantEntity>{
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
const { name, email } = createTenantDto;
const newTenant = new TenantEntity()
newTenant.name = name;
newTenant.email = email;
await newTenant.save()
return newTenant;
}
}
And here's where the error is triggered (tenants.service.ts)
// ...
import { TenantEntity } from './entities/tenant.entity';
import { TenantRepository } from './tenant.repository';
#Injectable()
export class TenantsService {
constructor(
#InjectRepository(TenantRepository)
private tenantRepository: TenantRepository
) { }
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
return await this.tenantRepository.createTenant(createTenantDto); // <-- ERROR
}
}
I can inject entity in service and use it for simple CRUD, but I want to separate concerns and use the repository pattern.
This is a POST endpoint and the error is only after submission from Swagger.
Also, VS Code autocomplete is suggesting createTenant after typing this.tenantRepository
Where am I going wrong?
EntityRepository decorator was deprecated, and as far as I know, you need to define a custom class that extends Repository and decorate it with #Injectable. Hence, you need to have some changes as follows:
tenant.repository.ts:
import { Injectable } from '#nestjs/common';
import { DataSource, Repository } from 'typeorm';
#Injectable()
export class TenantRepository extends Repository<TenantEntity>{
constructor(private dataSource: DataSource) {
super(TenantEntity, dataSource.createEntityManager());
}
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
const { name, email } = createTenantDto;
const newTenant = this.create({ name, email });
await this.save(newTenant);
return newTenant;
}
}
tenants.module.ts:
import { TenantRepository } from './tenant.repository';
#Module({
imports: [
TypeOrmModule.forFeature([TenantRepository]),
],
controllers: [TenantsController],
providers: [TenantsService, TenantRepository],
})
export class TenantsModule { }
tenants.service.ts:
import { TenantEntity } from './entities/tenant.entity';
import { TenantRepository } from './tenant.repository';
#Injectable()
export class TenantsService {
constructor(
private tenantRepository: TenantRepository
) { }
async createTenant(createTenantDto: CreateTenantDto): Promise<TenantEntity> {
return await this.tenantRepository.createTenant(createTenantDto);
}
}
You also have access to built-in typeorm methods like save, create, find, etc. since the custom repository is derived from Repository class.
[SOLVED] I'm pretty new to NestJS and trying to get my head around durable providers but i can't get them to work.
My scenario is that i have a service with some logic and two providers that implement the same interface to get some data. Depending on a custom header value i want to use Provider1 or Provider2 and the service itself does not have to know about the existing provider implementations.
Since i'm in a request scoped scenario but i know there are only 2 possible dependency-subtrees i want to use durable providers that the dependencies are not newly initialised for each request but reused instead.
I set up the ContextIdStrategy as described in the official docs and it is executed on each request but i miss the part how to connect my provider implementations with the ContextSubtreeIds created in the ContextIdStrategy.
Interface:
export abstract class ITest {
abstract getData(): string;
}
Implementations:
export class Test1Provider implements ITest {
getData() {
return "TEST1";
}
}
export class Test2Provider implements ITest {
getData() {
return "TEST2";
}
}
Service:
#Injectable()
export class AppService {
constructor(private readonly testProvider: ITest) {}
getHello(): string {
return this.testProvider.getData();
}
}
Controller:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getData(): string {
return this.appService.getData();
}
}
ContextIdStrategy:
const providers = new Map<string, ContextId>([
["provider1", ContextIdFactory.create()],
["provider2", ContextIdFactory.create()],
]);
export class AggregateByProviderContextIdStrategy implements ContextIdStrategy {
attach(contextId: ContextId, request: Request) {
const providerId = request.headers["x-provider-id"] as string;
let providerSubTreeId: ContextId;
if (providerId == "provider1") {
providerSubTreeId = providers["provider1"];
} else if (providerId == "provider2") {
providerSubTreeId = providers["provider2"];
} else {
throw Error(`x-provider-id ${providerId} not supported`);
}
// If tree is not durable, return the original "contextId" object
return (info: HostComponentInfo) =>
info.isTreeDurable ? providerSubTreeId : contextId;
}
}
Main:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
ContextIdFactory.apply(new AggregateByProviderContextIdStrategy());
await app.listen(3000);
}
bootstrap();
Module:
#Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: ITest,
useFactory: () => {
// THIS IS THE MISSING PIECE.
// Return either Test1Provider or Test2Provider based on the ContextSubtreeId
// which is created by the ContextIdStrategy
return new Test1Provider();
},
},
AppService,
],
})
export class AppModule {}
The missing part was a modification of the ContextIdStrategy return statement:
return {
resolve: (info: HostComponentInfo) => {
const context = info.isTreeDurable ? providerSubTreeId : contextId;
return context;
},
payload: { providerId },
}
after that change, the request object can be injected in the module and where it will only contain the providerId property and based on that, the useFactory statement can return different implementations
I want to get into unit testing and have some configuration services for my Nest API that I want to test. When starting the application I validate the environment variables with the joi package.
I have multiple configuration services for the database, the server, ... so I created a base service first. This one is able to read environment variables, parse the raw string to a desired datatype and validate the value.
import { ConfigService } from '#nestjs/config';
import { AnySchema, ValidationResult, ValidationError } from '#hapi/joi';
export abstract class BaseConfigurationService {
constructor(protected readonly configService: ConfigService) {}
protected constructValue(key: string, validator: AnySchema): string {
const rawValue: string = this.configService.get(key);
this.validateValue(rawValue, validator, key);
return rawValue;
}
protected constructAndParseValue<TResult>(key: string, validator: AnySchema, parser: (value: string) => TResult): TResult {
const rawValue: string = this.configService.get(key);
const parsedValue: TResult = parser(rawValue);
this.validateValue(parsedValue, validator, key);
return parsedValue;
}
private validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void {
const validationSchema: AnySchema = validator.label(label);
const validationResult: ValidationResult = validationSchema.validate(value);
const validationError: ValidationError = validationResult.error;
if (validationError) {
throw validationError;
}
}
}
Now I can extend this service with multiple configuration services. For the sake of simplicity I will take the server configuration service for this. Currently it only holds the port the application will listen to.
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import * as Joi from '#hapi/joi';
import { BaseConfigurationService } from './base.configuration.service';
#Injectable()
export class ServerConfigurationService extends BaseConfigurationService {
public readonly port: number;
constructor(protected readonly configService: ConfigService) {
super(configService);
this.port = this.constructAndParseValue<number>(
'SERVER_PORT',
Joi.number().port().required(),
Number
);
}
}
I found multiple articles out there that I should only test public methods, e.g.
https://softwareengineering.stackexchange.com/questions/100959/how-do-you-unit-test-private-methods
so I'm assuming I should not test the methods from the base configuration service. But I would like to test the classes extending the base service. I started with this
import { Test, TestingModule } from '#nestjs/testing';
import { ConfigService } from '#nestjs/config';
import { ServerConfigurationService } from './server.configuration.service';
const mockConfigService = () => ({
get: jest.fn(),
});
describe('ServerConfigurationService', () => {
let serverConfigurationService: ServerConfigurationService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ServerConfigurationService,
{
provide: ConfigService,
useFactory: mockConfigService
}
],
}).compile();
serverConfigurationService = module.get<ServerConfigurationService>(ServerConfigurationService);
});
it('should be defined', () => {
expect(serverConfigurationService).toBeDefined();
});
});
but as you can see in the second code snippet I'm calling the functions from the base service in the constructor. The test instantly fails with
ValidationError: "SERVER_PORT" must be a number
Is there a way I can unit test the configuration services although they depend on an abstract base class and an external .env file? Because I know I can create a mockConfigService but I think the base class breaks this. I don't know how to fix this test file.
The main problem boils down to this: You are using the Joi libary to parse environment variables. Whenever you call validateValue, Joi functions are called that expect actual environment variables to be set (in this case, SERVER_PORT). Now that these environment variables need to be set is a valid assumption for the running service. But in your test cases, you have no environment variables set, hence the Joi validation fails.
A primitive solution would be to set process.env.SERVER_PORT to some value in your beforeEach and delete it in afterEach. However, this is just a work-around around the actual issue.
The actual issue is: You hard-coded library calls into your BaseConfigurationService that have the assumption that environment variables are set. We already figured out earlier that this is not a valid assumption when running tests. When you stumble upon issues like this when writing tests, it often points to a problem of tight coupeling.
How can we address that?
We can separate the concerns clearly and abstract away the actual validation into its own service class that's used by BaseConfigurationService. Let's call that service class ValidationService.
We can then inject that service class into BaseConfigurationService using Nest's dependency injection.
When running tests, we can mock the ValidationService so it does not rely on actual environment variables, but, for example, just doesn't complain about anything during validation.
So here's how we can achieve that, step by step:
1. Define a ValidationService interface
The interface simply describes how a class needs to look that can validate values:
import { AnySchema } from '#hapi/joi';
export interface ValidationService {
validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void;
}
2. Implement the ValidationService
Now we'll take the validation code from your BaseConfigurationService and use it to implemente ValidationService:
import { Injectable } from '#nestjs/common';
import { AnySchema, ValidationResult, ValidationError } from '#hapi/joi';
#Injectable()
export class ValidationServiceImpl implements ValidationService {
validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void {
const validationSchema: AnySchema = validator.label(label);
const validationResult: ValidationResult = validationSchema.validate(value);
const validationError: ValidationError = validationResult.error;
if (validationError) {
throw validationError;
}
}
}
3. Inject ValidationServiceImpl into BaseConfigurationService
We'll now remove the validation logic from the BaseConfigurationService and instead add a call to ValidationService:
import { ConfigService } from '#nestjs/config';
import { AnySchema, ValidationResult, ValidationError } from '#hapi/joi';
import { ValidationServiceImpl } from './validation.service.impl';
export abstract class BaseConfigurationService {
constructor(protected readonly configService: ConfigService,
protected readonly validationService: ValidationServiceImpl) {}
protected constructValue(key: string, validator: AnySchema): string {
const rawValue: string = this.configService.get(key);
this.validationService.validateValue(rawValue, validator, key);
return rawValue;
}
protected constructAndParseValue<TResult>(key: string, validator: AnySchema, parser: (value: string) => TResult): TResult {
const rawValue: string = this.configService.get(key);
const parsedValue: TResult = parser(rawValue);
this.validationService.validateValue(parsedValue, validator, key);
return parsedValue;
}
}
4. Implemente a mock ValidationService
For testing purposes, we don't want to validate against actual environment variables, but just genereally accept all values. So we implement a mock service:
import { ValidationService } from './validation.service';
import { AnySchema, ValidationResult, ValidationError } from '#hapi/joi';
export class ValidationMockService implements ValidationService{
validateValue<TValue>(value: TValue, validator: AnySchema, label: string): void {
return;
}
}
5. Adapt classes extending BaseConfigurationService to have ConfigurationServiceImpl injected and pass it on to BaseConfigurationService:
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import * as Joi from '#hapi/joi';
import { BaseConfigurationService } from './base.configuration.service';
import { ValidationServiceImpl } from './validation.service.impl';
#Injectable()
export class ServerConfigurationService extends BaseConfigurationService {
public readonly port: number;
constructor(protected readonly configService: ConfigService,
protected readonly validationService: ValidationServiceImpl) {
super(configService, validationService);
this.port = this.constructAndParseValue<number>(
'SERVER_PORT',
Joi.number().port().required(),
Number
);
}
}
6. use the mock service in the test
Finally, now that ValidationServiceImpl is a dependency of BaseConfigurationService, we use the mocked version in the test:
import { Test, TestingModule } from '#nestjs/testing';
import { ConfigService } from '#nestjs/config';
import { ServerConfigurationService } from './server.configuration.service';
import { ValidationServiceImpl } from './validation.service.impl';
import { ValidationMockService } from './validation.mock-service';
const mockConfigService = () => ({
get: jest.fn(),
});
describe('ServerConfigurationService', () => {
let serverConfigurationService: ServerConfigurationService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ServerConfigurationService,
{
provide: ConfigService,
useFactory: mockConfigService
},
{
provide: ValidationServiceImpl,
useClass: ValidationMockService
},
],
}).compile();
serverConfigurationService = module.get<ServerConfigurationService>(ServerConfigurationService);
});
it('should be defined', () => {
expect(serverConfigurationService).toBeDefined();
});
});
Now when running the tests, ValidationMockService will be used. Plus, apart from fixing your test, you also have a clean separation of concerns.
The refactoring I provided here is just an example how you can go ahead. I guess that, depending on your further use cases, you might cut ValidationService differently than I did, or even separate more concerns into new service classes.
I'm new at typescript. My Nestjs project app is something like this. I'm trying to use repository pattern, so i separated business logic (service) and persistance logic (repository)
UserRepository
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from './entities/user.entity';
#Injectable()
export class UserRepo {
constructor(#InjectRepository(UserEntity) private readonly repo: Repository<UserEntity>) {}
public find(): Promise<UserEntity[]> {
return this.repo.find();
}
}
UserService
import { Injectable } from '#nestjs/common';
import { UserRepo } from './user.repository';
#Injectable()
export class UserService {
constructor(private readonly userRepo: UserRepo) {}
public async get() {
return this.userRepo.find();
}
}
UserController
import { Controller, Get } from '#nestjs/common';
import { UserService } from './user.service';
#Controller('/users')
export class UserController {
constructor(private readonly userService: UserService) {}
// others method //
#Get()
public async getUsers() {
try {
const payload = this.userService.get();
return this.Ok(payload);
} catch (err) {
return this.InternalServerError(err);
}
}
}
How do i create unit testing for repository, service & controller without actually persist or retrieve data to DB (using mock)?
Mocking in NestJS is pretty easily obtainable using the testing tools Nest exposes is #nestjs/testing. In short, you'll want to create a Custom Provider for the dependency you are looking to mock, and that's all there is. However, it's always better to see an example, so here is a possibility of a mock for the controller:
describe('UserController', () => {
let controller: UserController;
let service: UserService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: {
get: jest.fn(() => mockUserEntity) // really it can be anything, but the closer to your actual logic the better
}
}
]
}).compile();
controller = moduleRef.get(UserController);
service = moduleRef.get(UserService);
});
});
And from there you can go on and write your tests. This is pretty much the same set up for all tests using Nest's DI system, the only thing to be aware of is things like #InjectRepository() and #InjectModel() (Mongoose and Sequilize decorators) where you'll need to use getRepositoryToken() or getModelToken() for the injection token. If you're looking for more exmaples take a look at this repository
I created basic AuthGuard, but can't inject TokenService. I am getting this error:
Error: Nest can't resolve dependencies of the AuthGuard (?). Please verify whether [0] argument is available in the current context.
app.module.ts:
#Module({
modules: [
WorkModule,
],
components: [TokenService],
})
export class ApplicationModule { }
auth.guard.ts:
#Guard()
export class AuthGuard implements CanActivate {
constructor(
private readonly tokenService: TokenService,
) { }
public canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
work.module.ts:
#Module({
controllers: [WorkController],
components: [WorkService],
})
export class WorkModule { }
Update, work.service.ts:
import { Component, Inject, HttpStatus, HttpException } from '#nestjs/common';
const dataStore = require('nedb');
const workDB = new dataStore({ filename: '../db/work.db', autoload: true });
import * as moment from 'moment';
import { WorkDay, WorkDayDTO } from './work.model';
import { WorkHelpers } from './work.helpers';
#Component()
export class WorkService {
public async getWorkGraphic(month: number, year: number) {
return new Promise((resolve, reject) => {
// logic here
});
}
public async addOrUpdateWorkDay(day: WorkDayDTO) {
return new Promise((resolve, reject) => {
// logic here
});
}
public async removeWorkDay(workDayId: string) {
return new Promise((resolve, reject) => {
// logic here
});
}
}
But with this configuration everything is working:
#Module({
controllers: [
WorkController,
],
components: [TokenService, WorkService],
})
export class ApplicationModule { }
What exactly is causing this error and how can I get it work with 1st solution (Modules) ?
Is possible to show your TokenService and WorkerService?
You should register both always in your components to use inside all of application scope.
If you are using inside a specific module and trying to use in another module, probably you will not be able.
Another scenario. Imagine if you have A component registered in A module, B component registered in B module and imagine if ure trying to use A component inside of B module, you cant do that unless you register in Application Module or register inside A component inside B module(dont do that, only shared services should be used in all of the scopes, is just an architecture overview).