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))
}
Related
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.
I try to use Joi validator on NestJS with pipe.
https://docs.nestjs.com/pipes#object-schema-validation
import * as Joi from '#hapi/joi';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '#nestjs/common';
#Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(
private readonly schema: Joi.ObjectSchema,
) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = Joi.validate(value, this.schema);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
It doesn't work properly.
TypeError: Joi.validate is not a function
Use schema.validate in place of Joi.validate, for example:
const schema = Joi.object({
name: Joi.string().min(3).required()
});
const result = schema.validate(req.body);
or for more info go to https://hapi.dev/family/joi/?v=16.1.8
I have made an PR to update the https://docs.nestjs.com and it looks like it is already deployed, so you can refer to it.
#hapijs/joi deprecated Joi.validate with version 16 and you have to call .validate directly on schema.
I'm having some problem in resolving this error. When i add the Alarmservice i always get this error.
When i checked this error in google, every one say, i have to add this in provider, If i add in provider also it is not working and i get a different error
import { Component, Input } from '#angular/core';
import { Client, BasicAuth, AlarmService } from '#c8y/client';
import { CumulocityService } from './c8y.service';
import {
Router
} from '#angular/router';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
#Input() name: string;
model = {
user: '',
password: '',
tenant: ''
};
error = {
shown: false,
msg: ''
};
disabled = false;
basicAuth: string = 'https://tempar.adamos.com'
currentUser: object = {};
constructor(
private cumulocity: CumulocityService,
private alarmService: AlarmService,
private route: Router
) { }
async login() {
this.disabled = true;
const client = new Client(new BasicAuth(
{
user: this.model.user,
password: this.model.password,
tenant: this.model.tenant
}),
this.basicAuth
);
sessionStorage.setItem("client", JSON.stringify(client));
client.inventory.list$().subscribe((data) => {
// request all inventory data via fetch and adds realtime if data changes
console.log(data);
});
try {
let user = await client.user.current();
debugger;
this.cumulocity.client = client;
console.log(user.data);
const alarmId: number = 63704;
const { data, res } = await this.alarmService.detail(alarmId);
console.log(data, res);
// this.route.navigate(['/dashboard']);
} catch (ex) {
this.cumulocity.client = null;
this.error.shown = true;
this.error.msg = ex.message;
} finally {
this.disabled = false;
}
}
}
core.js:1673 ERROR Error: Uncaught (in promise): Error: StaticInjectorError(AppModule)[LoginComponent -> AlarmService]:
StaticInjectorError(Platform: core)[LoginComponent -> AlarmService]:
NullInjectorError: No provider for AlarmService!
Error: StaticInjectorError(AppModule)[LoginComponent -> AlarmService]:
StaticInjectorError(Platform: core)[LoginComponent -> AlarmService]:
NullInjectorError: No provider for AlarmService!
at NullInjector.push../node_modules/#angular/core/fesm5/core.js.NullInjector.get (core.js:1062)
at resolveToken (core.js:1300)
at tryResolveToken (core.js:1244)
at StaticInjector.push../node_modules/#angular/core/fesm5/core.js.StaticInjector.get (core.js:1141)
at resolveToken (core.js:1300)
at tryResolveToken (core.js:1244)
at StaticInjector.push../node_modules/#angular/core/fesm5/core.js.StaticInjector.get (core.js:1141)
at resolveNgModuleDep (core.js:8376)
at NgModuleRef_.push../node_modules/#angular/core/fesm5/core.js.NgModuleRef_.get (core.js:9064)
at resolveDep (core.js:9429)
at NullInjector.push../node_modules/#angular/core/fesm5/core.js.NullInjector.get (core.js:1062)
at resolveToken (core.js:1300)
at tryResolveToken (core.js:1244)
at StaticInjector.push../node_modules/#angular/core/fesm5/core.js.StaticInjector.get (core.js:1141)
at resolveToken (core.js:1300)
at tryResolveToken (core.js:1244)
at StaticInjector.push../node_modules/#angular/core/fesm5/core.js.StaticInjector.get (core.js:1141)
at resolveNgModuleDep (core.js:8376)
at NgModuleRef_.push../node_modules/#angular/core/fesm5/core.js.NgModuleRef_.get (core.js:9064)
at resolveDep (core.js:9429)
at resolvePromise (zone.js:814)
at resolvePromise (zone.js:771)
at zone.js:873
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:3815)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
at drainMicroTaskQueue (zone.js:595)
If i add Alarmservice at the app.module.ts I get a different error, as said above
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { CumulocityService } from './login/c8y.service';
import { AlarmService } from '#c8y/client';
#NgModule({
declarations: [
AppComponent,
LoginComponent,
DashboardComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
],
providers: [ AlarmService, CumulocityService],
bootstrap: [AppComponent]
})
export class AppModule { }
ERROR
Uncaught Error: Can't resolve all parameters for AlarmService: (?, ?).
at syntaxError (compiler.js:1016)
at CompileMetadataResolver.push../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getDependenciesMetadata (compiler.js:10917)
at CompileMetadataResolver.push../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getTypeMetadata (compiler.js:10810)
at CompileMetadataResolver.push../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getInjectableTypeMetadata (compiler.js:11032)
at CompileMetadataResolver.push../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getProviderMetadata (compiler.js:11041)
at compiler.js:10979
at Array.forEach (<anonymous>)
at CompileMetadataResolver.push../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getProvidersMetadata (compiler.js:10939)
at CompileMetadataResolver.push../node_modules/#angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNgModuleMetadata (compiler.js:10658)
at JitCompiler.push../node_modules/#angular/compiler/fesm5/compiler.js.JitCompiler._loadModules (compiler.js:23858)
Have you seen the stackblitz example? I guess you have because it looks like the code from there.
At the moment you cannot inject the services into angular directly. You can just write a wrapper around the client (like done in the example)
So to make your code working:
remove the AlarmService import
Request a alarm via this.client.alarm.detail(alarmId); instead of the AlarmService.
The this.client.alarm is of type AlarmService and uses the login you set via new Client(new BasicAuth([...]));.
In future, you can use #c8y/ngx-data to do proper dependency injection, but it is not released as stable and therefore I would not suggest to use it.
I could use a little help.
Context I am making a angular 5 module for an authentication service.
How do I pass a POJO into a class as parameters?
See my answer below.
I need to use HttpClient inside this auth service
I am getting this error:
Error: Can't resolve all parameters for AuthService: ([object Object], ?).
[object Object] is angular 5 HttpClient
Can some one please explain why I am getting this error and how to resolve it?
#NgModule()
export class AuthtModule {
static forRoot(params?: iParams) {
return {
ngModule: AuthModule,
provides: [
HttpClient,
{
provide: AuthService,
useFactory: setupAuthService,
deps: [ HttpClient, params ]
}
],
imports: [ HttpClientModule ],
exports: [ AuthService ]
}
}
}
Thanks in advance.
Cheers
I figured out how to do this, as it turns out the problem was with the second parameter (hence the question mark) not the first. the reason this error occurs is because in order for parameters to be passed into a class they must first be turned into a injectable.
Here is how you do it.
First create a class model with the params
foo-params.model.ts
export class FooParams {
public foo1: string;
public foo2: number;
}
Then In the Module class attributes set the class FooParams to use the values that are a POJO
app.module.ts
import { HttpClientModule, HttpClient } from '#angular/common/http';
import { FooParams } from './foo-params';
#NgModule({
imports : [ HttpClientModule ],
providers: [{
HttpClient
{ provider: FooParams, useValue: params },
{
provider: BarService,
useFactory: setupBarService
deps: [ HttpClient, FooParams ]
}
}]
})
export class AppModule {}
And this is what the class that consumes the pojo would look like.
bar.service.ts
import { HttpClient } from '#angular/common/http';
import { FooParams } from './foo.service.ts';
expecto function setupBarService(http: HttpClient, params: FooParams) {
return new BarService(http, params);
}
#Injectable()
export class BarService {
constructor(http: HttpClient, params: FooParams) {}
//DO STUFF
}
Try by importing import 'core-js/es7/reflect'; in polyfills.ts
I am currently writing tests for my Angular2 (with Typescript) application and all has been fine and dandy so far, that is until I have attempted to start testing one of my services.
This service has the Angular2 Http module injected on instantiation as shown below:
import { Injectable, EventEmitter } from 'angular2/core';
import { Http } from 'angular2/http';
import 'rxjs/add/operator/map';
import { ConfigObject } from '../ConfigObject';
import { HTTPHelper } from '../helpers/HTTPHelper';
import { Category } from '../classes/Category';
#Injectable()
export class CategoryService {
public emitter: EventEmitter<Category>;
constructor(private _http: Http) {
this.emitter = new EventEmitter();
}
private APIUrl = ConfigObject.productBox + ConfigObject.apiVersion + 'category';
getCategories(filters) {
return this._http.get(this.APIUrl + HTTPHelper.convertVarsToString(filters))
.map(res => res.json());
}
public emitCat(category): void {
this.emitter.emit(category);
}
}
This is then used to make GET requests to an API box I have created.
Here is my Jasmine test spec file for the service:
import { CategoryService } from '../../services/category.service';
import { Http } from 'angular2/http';
describe('Category service', () => {
let testCategoryService: CategoryService;
let _http: Http;
beforeEach(function() {
testCategoryService = new CategoryService(Http);
});
it('should have emitter name set', () => {
expect(testCategoryService.emitter._isScalar).toBeDefined();
});
it('should return categories', () => {
testCategoryService.getCategories({
order : 'asc',
order_by : 'name',
parent_id : 0,
});
});
});
As you can see, am including the Http object here too and injecting it into the test instantiation of my service class before each test on this line:
beforeEach(function() {
testCategoryService = new CategoryService(Http);
});
When I try and test the 'getCategories' function on my service class I get the following error:
TypeError: this._http.get is not a function
Which is odd as as far as I am concerned I am injecting the Http service into my test instantiation on the line above so this should be set in the class constructor?
Can anyone see why the Http object in my class is not being set?
Thanks