NestJS Validate Headers using class-transform example - nestjs

I am trying to validate that the headers of the request contain some specific data, and I am using NestJS. I found this information. While this is what I want to do, and it looks proper, the ClassType reference does not exist, and I am not sure what to use instead.
From the example, the decorator is referring to.
request-header.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/commom'
import { plainToClass } from 'class-transformer';
// The import below fails
import { ClassType } from 'class-transformer/ClassTransformer';
import { validateOrReject } from 'class-validator';
export const RequestHeader = createParamDecorator(
async (value: ClassType<unknown>, ctx: ExecutionContext) => {
// extract headers
const headers = ctx.switchToHttp().getRequest().headers;
// Convert headers to DTO object
const dto = plainToClass(value, headers, { excludeExtraneousValues: true });
// Validate
await validateOrReject(dto);
// return header dto object
return dto;
},
);

Rather than passing the type through a decorator like this, I'd suggest making a custom decorator and setting the validateCustomDecorators option for the ValidationPipe to true. The decorator would look something like
const Header = createParamDecorator((data: unknown, context: ExecutionContext) => {
const req = context.switchToHttp().getRequest();
if (data) {
return req.headers[data];
}
return req.headers;
});
And now instead of #Header() from #nestjs/common you can you #Header() from this file and get the ValidationPipe to run after applying the appropriate type metadata

Related

How to create common class for third-party API requests in NestJS

I am creating NestJS application where I am making third-party API requests. For that I have to write the same thing inside every function in order to get the data.
To make things non-repeating, how can I write on common class that has API request based on GET or POST request and send the response so that I can use that class in every function.
Below is my code:
subscribe.service.ts
#Injectable()
export class SubscribeService {
constructor(#InjectModel('Subscribe') private readonly model:Model<Subscribe>,
#Inject(CACHE_MANAGER) private cacheManager:Cache,
private httpService: HttpService){}
async addSubscriber(subscriberDto:SubscribeDto){
const url = 'https://track.cxipl.com/api/v2/phone-tracking/subscribe';
const headersRequest = {
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
};
try{
const resp = await this.httpService.post(url,subscriberDto,{ headers: headersRequest }).pipe(
map((response) => {
if(response.data.success == true){
const data = new this.model(subscriberDto);
// return data.save();
const saved = data.save();
if(saved){
const msgSuccess = {
"success":response.data.success,
"status":response.data.data.status
}
return msgSuccess;
}
}
else{
const msgFail = {"success":response.data.success}
return msgFail;
}
}),
);
return resp;
}
catch(err){
return err;
}
}
async getLocation(phoneNumber:PhoneNumber){
try{
const location = await this.cacheManager.get<Coordinates>(phoneNumber.phoneNumber);
if(location){
return location;
}
else{
const resp = await axios.post('https://track.cxipl.com/api/v2/phone-tracking/location',phoneNumber,{headers:{
'content-Type': 'application/json',
'authkey': process.env.AUTHKEY
}});
const msg:Coordinates = {
"location":resp.data.data.location,
"timestamp":resp.data.data.timestamp
}
await this.cacheManager.set<Coordinates>(phoneNumber.phoneNumber,msg, { ttl: 3600 });
return msg;
}
}
catch(err){
console.log(err);
return err;
}
}
}
As in above code in both function addSubscriber() and getLocation() I need to hit the API repeatedly and add request headers again and again is there any way so that I can create one separate class for request and response and utilize in my service.
How can I achieve desired the result?
To create a common class for making third-party API requests in NestJS, you can follow these steps:
Create a new file in your NestJS project to store the common class.
For example, you could create a file called api.service.ts in the
src/common directory.
In the file, create a new class called ApiService that will be responsible for making the API requests. This class should have a
constructor that injects the necessary dependencies, such as the
HttpService provided by NestJS.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
}
Add methods to the ApiService class for each type of API request you want to make. For example, you might have a get() method for making GET requests, a post() method for making POST requests, and so on. Each method should accept the necessary parameters for making the request (such as the URL and any query parameters or request body), and use the HttpService to make the request.
import { HttpService, Injectable } from '#nestjs/common';
#Injectable()
export class ApiService {
constructor(private readonly httpService: HttpService) {}
async get(url: string, params?: object): Promise<any> {
return this.httpService.get(url, { params }).toPromise();
}
async post(url: string, body: object): Promise<any> {
return this.httpService.post(url, body).toPromise();
}
}
Inject the ApiService wherever you need to make API requests. For example, you might inject it into a service or a controller, and use the methods of the ApiService to make the actual API requests.
import { Injectable } from '#nestjs/common';
import { ApiService } from './api.service';
#Injectable()
export class SomeService {
constructor(private readonly apiService: ApiService) {}
async getData(): Promise<any> {
return this.apiService.get('https://some-api.com/endpoint');
}
}
This is just one way you could create a common class for making third-party API requests in NestJS. You can customize the ApiService class to meet the specific needs of your application

How to set default header in axios with Typescript

I want to set default header with axios in Typescript.
Here is my function:
function setJwt(jwt: string | null) {axios.defaults.headers!.common["x-auth-token"] = jwt;}
This shows the following issue:
How to solve that in Typescript?
I've fixed this by extending HeadersDefaults interface from axios.
import axios, { HeadersDefaults } from 'axios'
interface CommonHeaderProperties extends HeadersDefaults {
Authorization: string;
}
axiosInstance.defaults.headers = {
Authorization: `Bearer authToken`
} as CommonHeaderProperties;
I succeed to solve the problem.
Well, headers property has a Record<string, string> type.
So, we should change the second generic type like this:
We cast to unknown first because we can't reconvert directy the type Record
In the past (with axios v0.27.2) I did it like this:
import axios, { AxiosRequestHeaders } from 'axios';
interface CustomAxiosRequestHeaders extends AxiosRequestHeaders {
'x-auth-token': string;
'api-lang': string;
}
const instance = axios.create();
instance.interceptors.request.use((request) => {
request.headers = {
'x-auth-token': getCurentJWT(),
'api-lang': getLang(),
} as CustomAxiosRequestHeaders;
// ...
return request;
});
Now (with axios v1.2.3) I'm doing it the following way:
import axios from 'axios';
const instance = axios.create();
instance.interceptors.request.use((request) => {
request.headers.set('x-auth-token', getCurentJWT());
request.headers.set('api-lang', getLang());
// ...
return request;
});
You can try like below,
function setJwt(jwt: string | null) {
if (axios.defaults?.headers && axios.defaults.headers.common) {
axios.defaults.headers.common['x-auth-token'] = jwt;
}
}
How to resolve with the declare module.
I wanted to define the basic typescript in HeadersDefaults instead of axiosInstance, so I applied it to the root path as follows.
interface Authorization {
Authorization: string;
}
declare module 'axios' {
export interface HeadersDefaults extends Authorization {}
}

How can i test this decorator?

Thats basically the model of the code. I tried somethings but i`m new at testing, so the lines isnt been covered.
So, I don't have idea of how I can acess and testing the content from createParamDecorator(). Or of how I test the headers, dto, and everything.
I tried to mock de ExecutionContext value and the switchToHttp() and put everything to return null and the test pass. But the lines wasn't covered.
import { plainToClass } from 'class-transformer';
// The import below fails
import { ClassType } from 'class-transformer/ClassTransformer';
import { validateOrReject } from 'class-validator';
export const RequestHeader = createParamDecorator(
async (value: any, ctx: ExecutionContext) => {
// extract headers
const headers = ctx.switchToHttp().getRequest().headers;
// Convert headers to DTO object
const dto = plainToClass(value, headers, { excludeExtraneousValues: true });
// Validate
await validateOrReject(dto);
// return header dto object
return dto;
},
);

Nestjs - Use applyDecorators inside createParamDecorator

I'm using Nestjs decorators and am trying to make the most of custom decorators. I'm trying to write my own custom #Body param decorator that validates and applies multiple decorators at the same time.
Does anyone know if the below is possible? I'm having difficulty getting the second argument in the transform call of the pipes to have metadata: ArgumentMetadata.
export const MyParamDecorator = <T>(myDto: T) => {
return createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
applyDecorators( // also get SetMeta and Pipes to validate DTO
SetMetadata('thisWorks', true)
UsePipes(CustomValidationPipe, OtherPipe), // + add MyDTO - type T somehow..
);
return doAsyncWork()
},
)();
}
#Controller('users')
export class UsersController {
#Patch(':id')
update(#MyParamDecorator() asyncWork: Promise<any>) { // <------ Promise<any> is custom async opperation that will be handled. (So I can't type the DTO here..)
return reqBody;
}
}
I ran across this question because I needed a similar answer. Hopefully what I've found is helpful.
Part 1. You can do async processing in a decorator.
And, it would resolve for you, so your controller would look like:
update(#MyParamDecorator() asyncWork: any) {
Notice that Promise<any> is just any.
Part 2. You can get ArgumentMetadata using an enhancer.
Here is a quick example, let's assume METADATA__PARAM_TYPE is myType.
param-type.enhancer.ts
export const paramTypeEnhancer: ParamDecoratorEnhancer = (
target: Record<string, unknown>,
propertyKey: string,
parameterIndex: number,
): void => {
const paramTypes = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey);
const metatype = paramTypes[parameterIndex];
Reflect.defineMetadata(METADATA__PARAM_TYPE, metatype, target[propertyKey]);
};
my-decorator.ts
import { paramTypeEnhancer } from './param-type.enhancer';
export const MyDecorator = createParamDecorator(
async (data: unknown, ctx: ExecutionContext): Promise<any> => {
const metatype = Reflect.getOwnMetadata(METADATA__PARAM_TYPE, ctx.getHandler());
const argument: ArgumentMetadata = {
type: 'custom',
data: undefined,
metatype: metatype,
};
// Do processing here... You can return a promise.
},
[paramTypeEnhancer],
);
See this gist for a full annotated version: https://gist.github.com/josephdpurcell/fc04cfd428a6ee9d7ffb64685e4fe3a6

Create a Header Custom Validation with NestJS and class-validator

I have been working to validate a request using the class-validator, and NestJS validation plus trying to validate the header contents. My basic interfaces are all working, but now I am trying to compare some header field data the same way.
I had this question about the custom decorator to try to handle the headers, but the solution to that question, will return the one header. I want to be able to handle them all, similar to how all the body() data is processed.
I need to be able to create a custom decorator for extracting the header fields, and being able to pass them into the class-validator DTO.
For Instance, I want to validate three header fields, such as:
User-Agent = 'Our Client Apps'
Content-Type = 'application/json'
traceabilityId = uuid
There are more fields, but if I can get this going, then I can extrapolate out the rest. I have a simple controller example:
#Controller(/rest/package)
export class PackageController {
constructor(
private PackageData_:PackageService
)
{ }
...
#Post('inquiry')
#HttpCode(HttpStatus.OK) // Not creating data, but need body, so return 200 OK
async StatusInquiry(
#RequestHeader() HeaderInfo:HeadersDTO, // This should be the Headers validation using the decorator from the question above.
I am trying to validate that the headers of the request contain some specific data, and I am using NestJS. I found this information. While this is what I want to do, and it looks proper, the ClassType reference does not exist, and I am not sure what to use instead.
From the example, the decorator is referring to.
request-header.decorator.ts
export interface iError {
statusCode:number;
messages:string[];
error:string;
}
export const RequestHeader = createParamDecorator(
async (value: any, ctx: ExecutionContext) => {
// extract headers
const headers = ctx.switchToHttp().getRequest().headers;
// Convert headers to DTO object
const dto = plainToClass(value, headers, { excludeExtraneousValues: true });
// Validate
const errors: ValidationError[] = await validate(dto);
if (errors.length > 0) {
let ErrorInfo:IError = {
statusCode: HttpStatus.BAD_REQUEST,
error: 'Bad Request',
message: new Array<string>()
};
errors.map(obj => {
AllErrors = Object.values(obj.constraints);
AllErrors.forEach( (OneError) => {
OneError.forEach( (Key) => {
ErrorInfo.message.push(Key);
});
});
// Your example, but wanted to return closer to how the body looks, for common error parsing
//Get the errors and push to custom array
// let validationErrors = errors.map(obj => Object.values(obj.constraints));
throw new HttpException(`${ErrorInfo}`, HttpStatus.BAD_REQUEST);
}
// return header dto object
return dto;
},
I am having trouble generically mapping the constraints into a string array.
My HeadersDTO.ts:
import { Expose } from 'class-transformer';
import { Equals, IsIn, IsString } from 'class-validator';
export class HeadersDTO {
#IsString()
#Equals('OurApp')
#Expose({ name: 'user-agent' })
public readonly 'user-agent':string;
#IsString()
#IsIn(['PRODUCTION', 'TEST'])
public readonly operationMode:string;
}
Headers being sent via Postman for the request:
Content-Type:application/json
operationMode:PRODUCTION
Accept-Language:en
I have just tested the following code and this is working. I think you are missing appropriate type over here,
async StatusInquiry(
#RequestHeader() HeaderInfo:HeadersDTO,
You should have HeadersDTO passed in as param in RequestHeader Decorator this #RequestHeader(HeadersDTO) HeaderInfo:HeadersDTO,
Then I have created customDecorator.ts like this,
export const RequestHeader = createParamDecorator(
//Removed ClassType<unknown>,, I don't think you need this here
async (value: any, ctx: ExecutionContext) => {
// extract headers
const headers = ctx.switchToHttp().getRequest().headers;
// Convert headers to DTO object
const dto = plainToClass(value, headers, { excludeExtraneousValues: true });
// Validate
const errors: ValidationError[] = await validate(dto);
if (errors.length > 0) {
//Get the errors and push to custom array
let validationErrors = errors.map(obj => Object.values(obj.constraints));
throw new HttpException(`Validation failed with following Errors: ${validationErrors}`, HttpStatus.BAD_REQUEST);
}
// return header dto object
return dto;
},
);
My HeadersDTO.ts file
export class HeadersDTO
{
#IsDefined()
#Expose({ name: 'custom-header' })
"custom-header": string; // note the param here is in double quotes
}
The reason i found this when I looked compiled TS file, it looks like this,
class HeadersDTO {
}
tslib_1.__decorate([
class_validator_1.IsDefined(),
class_transformer_1.Expose({ name: 'custom-header' }),
tslib_1.__metadata("design:type", String)
], HeadersDTO.prototype, "custom-header", void 0);
exports.HeadersDTO = HeadersDTO;
I get following error when i do not pass the header ,
[
ValidationError {
target: HeadersDTO { 'custom-header': undefined },
value: undefined,
property: 'custom-header',
children: [],
constraints: { isDefined: 'custom-header should not be null or undefined' }
}
]
As mentioned in the issue you referenced, you need to create a DTO class and pass it to RequestHeader decorator.
e.g
export class MyHeaderDTO {
#IsString()
#IsDefined()
#Expose({ name: 'myheader1' }) // required as headers are case insensitive
myHeader1: string;
}
...
#Get('/hello')
getHello(#RequestHeader(MyHeaderDTO) headers: MyHeaderDTO) {
console.log(headers);
}

Resources