Combine Multiple Param Decorator - javascript

I am trying to combine multiple Parameter Decorator for my application.
In my application there are several custom decorators.
My Sample Controller.
#Controller('api')
export class MyController {
#Get('mainresponse')
public getResponse(
#Query() query: Partial<IBean>,
#Headers('user-agent') userAgent: string,
#MyFirstDocrator() firstDecorator: string,
#MySecondDocrator() secondDecorator: string,
#Headers('Referer') referer: string,
#Cookies('session') sessionId?: string,
): Observable<IResponse> {
......
}
}
I am trying to combine these decorators into 1 and return an object with all the decorators value.
Something like this
#Get('mainresponse')
public getResponse(
#MyCombinedDecorator() decorator: any
): Observable<IResponse> {
......
}
But when I am using applyDecorators it is giving me the return value of the last passed decorator.
export const MyCombinedDecorator = () => applyDecorators(Headers('user-agent') as PropertyDecorator, MyFirstDocrator() as PropertyDecorator, Query() as PropertyDecorator);

Related

Enforcing return values with Typescript

I have a class that has two methods for generating and decoding jsonwebtokens. This is what the class looks like.
interface IVerified {
id: string
email?: string
data?: any
}
export default class TokenProvider implements ITokenProvider {
public async generateToken({ id, email }: ITokenDetails): Promise<string> {
return sign({ id, email }, CONFIG.JWT.secret, {
subject: id,
expiresIn: CONFIG.JWT.expires,
})
}
public async decodeToken(token: string): Promise<IVerified> {
const user = verify(token, CONFIG.JWT.secret)
return user as IVerified
}
}
In the decodeToken method, as you can see from above, it takes in a token and is meant to return the decoded values embedded in the token. But vscode Intellisense shows that jwt.verify() method returns a string | object. How can I override this to enforce the method to return some user attributes? Instead of getting string | object, I want to return the attributes described by the IVerified interface stated above. Any help is appreciated! Thank you very much.
You generally have two options:
Coerce the result to a IVerified like you do there (you might have to do return (user as any) as IVerified; though to get typescript to do what you want. This is fine as long as you can guarantee that the object returned from jwt.verify adheres to the IVerified interface.
Create a helper function that takes in a string | object and does the necessary logic to do runtime validation, deserialization, etc, in order to ensure you get an IVerified back:
private function validateDecodedToken(input: string | object): IVerified {
// ... do whatever you need to parse/deserialize/validate/etc here
}
public async decodeToken(token: string): Promise<IVerified> {
const user = verify(token, CONFIG.JWT.secret);
return this.validateDecodedToken(user);
}
This is the more "robust" approach, but might be overkill if there
are other guarantees in the system.

How to initialize a typed Object in TypeScript/Angular?

I'm new to Angular & TypeScript and trying to figure out how to instantiate an object (before an api request is returned with the real data).
For example, my model looks like this:
//order.model.ts
export class Order {
constructor(public id: number, currency: string, public contact: Object, public items: Array<Object>) {}
}
And then I try to instantiate that in one of my components, let's say the App component:
//app.component.ts
export class AppComponent {
#Input()
public order: Order = new Order();
}
Of course, it expected to receive 4 arguments when instantiating new Order() but received 0. Do I actually have to pass in undefined/empty values for each attribute of Order?
In good ol' React (without TS) I would just initialize with an empty object and call it a day:
this.state = {
order: {}
}
What's best practice for this sort of thing in Angular/TS?
Yes as it is currently set up you would have to pass 4 default arguments to the constructor.
public order: Order = new Order(1, '', {}, []);
Or you can set each property as nullable by adding a ? like so:
export class Order {
constructor(public id?: number, currency?: string, public contact?: Object, public items?: Array<Object>) {}
}
If the class doesn't have functionality (you are simply using it for type checking) the best way to do it would be to declare an interface like so (you can also make them nullable here with ?s):
export interface Order {
id: number;
currency: string;
contact: Object;
items: Object[];
}
then in your component do not initialize the value until you have all of the needed values:
//app.component.ts
export class AppComponent {
#Input()
public order: Order;
// just an example
setValues(id: number, currency: string, contact: Object, items: Object[]) {
this.order = {
id: id,
currency: currency,
contact: contact,
items: items
}
}
// example for if you receive object with correct fields from backend
getData() {
this.service.getData().subscribe(result => {
this.order = result;
});
}
}

How to use validation in NestJs with HTML rendering?

NestJS uses validation with validation pipes and
#UsePipes(ValidationPipe)
If this fails it throws an exception. This is fine for REST APIs that return JSON.
How would one validate parameters when using HTML rendering and return
{ errors: ['First error'] }
to an hbs template?
You can create an Interceptor that transforms the validation error into an error response:
#Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
return call$.pipe(
// Here you can map (or rethrow) errors
catchError(err => ({errors: [err.message]}),
),
);
}
}
You can use it by adding #UseInterceptors(ErrorsInterceptor) to your controller or its methods.
I've been driving myself half mad trying to find a "Nest like" way to do this while still retaining a degree of customisability, and I think I finally have it. Firstly, we want an error that has a reference to the exisiting class-validator errors, so we create a custom error class like so:
import { ValidationError } from 'class-validator';
export class ValidationFailedError extends Error {
validationErrors: ValidationError[];
target: any;
constructor(validationErrors) {
super();
this.validationErrors = validationErrors;
this.target = validationErrors[0].target
}
}
(We also have a reference to the class we tried to validate, so we can return our object as appropriate)
Then, in main.ts, we can set a custom exception factory like so:
app.useGlobalPipes(
new ValidationPipe({
exceptionFactory: (validationErrors: ValidationError[] = []) => {
return new ValidationFailedError(validationErrors);
},
}),
);
Next, we create an ExceptionFilter to catch our custom error like so:
#Catch(ValidationFailedError)
export class ValidationExceptionFilter implements ExceptionFilter {
view: string
objectName: string
constructor(view: string, objectName: string) {
this.view = view;
this.objectName = objectName;
}
async catch(exception: ValidationFailedError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
response.render(this.view, {
errors: exception.validationErrors,
[this.objectName]: exception.target,
url: request.url,
});
}
}
We also add an initializer, so we can specify what view to render and what the object's name is, so we can set up our filter on a controller method like so:
#Post(':postID')
#UseFilters(new ValidationExceptionFilter('blog-posts/edit', 'blogPost'))
#Redirect('/blog-posts', 301)
async update(
#Param('id') postID: string,
#Body() editBlogPostDto: EditBlogPostDto,
) {
await this.blogPostsService.update(postID, editBlogPostDto);
}
Hope this helps some folks, because I like NestJS, but it does seem like the docuemntation and tutorials are much more set up for JSON APIs than for more traditional full stack CRUD apps.

How to use query parameters in Nest.js?

I am a freshman in Nest.js.
And my code as below
#Get('findByFilter/:params')
async findByFilter(#Query() query): Promise<Article[]> {
}
I have used postman to test this router
http://localhost:3000/article/findByFilter/bug?google=1&baidu=2
Actually, I can get the query result { google: '1', baidu: '2' }. But I'm not clear why the url has a string 'bug'?
If I delete that word just like
http://localhost:3000/article/findByFilter?google=1&baidu=2
then the postman will shows statusCode 404.
Actually, I don't need the word bug, how to custom the router to realize my destination just like http://localhost:3000/article/findByFilter?google=1&baidu=2
Here's another question is how to make mutiple router point to one method?
Query parameters
You have to remove :params for it to work as expected:
#Get('findByFilter')
async findByFilter(#Query() query): Promise<Article[]> {
// ...
}
Path parameters
The :param syntax is for path parameters and matches any string on a path:
#Get('products/:id')
getProduct(#Param('id') id) {
matches the routes
localhost:3000/products/1
localhost:3000/products/2abc
// ...
Route wildcards
To match multiple endpoints to the same method you can use route wildcards:
#Get('other|te*st')
will match
localhost:3000/other
localhost:3000/test
localhost:3000/te123st
// ...
If you have you parameter as part or url: /articles/${articleId}/details, you wold use #Param
#Get('/articles/:ARTICLE_ID/details')
async getDetails(
#Param('ARTICLE_ID') articleId: string
)
IF you want to provide query params /article/findByFilter/bug?google=1&baidu=2, you could use
#Get('/article/findByFilter/bug?')
async find(
#Query('google') google: number,
#Query('baidu') baidu: number,
)
We can use #Req()
import { Controller, Get, Req } from '#nestjs/common';
import { Request } from 'express';
(...)
#Get(':framework')
getData(#Req() request: Request): Object {
return {...request.params, ...request.query};
}
/nest?version=7
{
"framework": "nest",
"version": "7"
}
read more
You can use the #Req decorator, and use param object, see :
#Get()
findAll(
#Req() req: Request
): Promise<any[]> {
console.log(req.query);
// another code ....
}
For better explaining I wrote a pagination example with number transformer class:
class QueryDto {
#Type(() => Number)
#IsInt()
public readonly page: number;
#Type(() => Number)
#IsInt()
public readonly take: number;
}
#Injectable()
class QueryTransformPipe implements PipeTransform {
async transform(value: QueryRequestDto, { metatype }: ArgumentMetadata) {
if (!metatype) {
return value;
}
return plainToInstance(metatype, value);
}
}
#Controller()
class YourController {
#Get()
// also you can use it with pipe decorator
// #UsePipes(new QueryTransformPipe())
public async getData(#Query(new QueryTransformPipe()) query?: QueryRequestDto): Promise<any[]> {
// here you get instanceof QueryTransformPipe
// and typeof query.page === 'number' && typeof query.take === 'number'
}
}

How to make param required in NestJS?

I would like to make my route Query parameter required.
If it is missing I expect it to throw 404 HTTP error.
#Controller('')
export class AppController {
constructor() {}
#Get('/businessdata/messages')
public async getAllMessages(
#Query('startDate', ValidateDate) startDate: string,
#Query('endDate', ValidateDate) endDate: string,
): Promise<string> {
...
}
}
I'm using NestJs pipes to determine if a parameter is valid, but not if it exists And I'm not sure that Pipes are made for that.
So how can I check in NestJS if my param exists if not throw an error?
Use class-validator. Pipes are definitely made for that !
Example :
create-user.dto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateUserDto {
#IsNotEmpty()
password: string;
}
For more information see class-validator documentation :
https://github.com/typestack/class-validator
And NestJS Pipes & Validation documentation :
https://docs.nestjs.com/pipes
https://docs.nestjs.com/techniques/validation
NestJS does not provide a decorator (like #Query) that detects undefined
value in request.query[key].
You can write custom decorator for that:
import { createParamDecorator, ExecutionContext, BadRequestException } from '#nestjs/common'
export const QueryRequired = createParamDecorator(
(key: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
const value = request.query[key]
if (value === undefined) {
throw new BadRequestException(`Missing required query param: '${key}'`)
}
return value
}
)
Then use #QueryRequired decorator as you would use #Query:
#Get()
async someMethod(#QueryRequired('requiredParam') requiredParam: string): Promise<any> {
...
}
There hava a easy way to valide you parameter, https://docs.nestjs.com/techniques/validation
In addition to Phi's answer, you can combine the use of class-validator with the following global validation pipe:
app.useGlobalPipes(
new ValidationPipe({
/*
If set to true, instead of stripping non-whitelisted
properties validator will throw an exception.
*/
forbidNonWhitelisted: true,
/*
If set to true, validator will strip validated (returned)
object of any properties that do not use any validation decorators.
*/
whitelist: true,
}),
);
I use this in order to only allow parameters defined in the DTO class so that it will throw an error when unknown parameters are sent with the request!
In Phie's example, a post request with a body like {password: 'mypassword'} will pass the validation when {password: 'mypassword', other: 'reject me!'} won't.

Categories