How to use Get request with DTO in NestJs? - node.js

I am trying to write a function to handle the Get request, here is my code:
#Get('/find')
async find(#Param() testname: NameDto) {
console.log(testname.name);
}
Here is my dto:
export class NameDto {
#IsString()
#ApiProperty({ required: true })
name: string;
}
I am using the Swagger to test this API :
When I input a signle a , I got the following response:
{
"statusCode": 400,
"message": [
"name must be a string"
],
"error": "Bad Request"
}
here are more input example :
They all return the same response.
Then, I change the find function like this with #Query:
#Get('/find')
async find(#Query() testname: NameDto) {
console.log(testname.name);
}
here is my input :
I can have the 200-ok response.
Here is another example:
I enter 1 as the input, and I still can get the 200 response. The dto is not working as expected.
Am I missing something?
Any help would be appreciate.

you are sending nothing in param
you can read in Nestjs document
try this one:
#Get('/find/:testname')
async find(#Param('testname') testname: NameDto) {
console.log(testname.name);
}

You're using query parameters for the url, so you do need to use #Query(). Query parameters will always come in as strings, because that's how express and fastify work. You can enable transform: true and {transformOptions: { enableImplicitConverion: true } } in the ValidationPipe's options and get numbers to show up as actual numbers.

you can do something like this it's works for me:
//find-one.dto.ts
import { IsNotEmpty, IsUUID } from 'class-validator';
export class FindOneParams {
#IsUUID()
#IsNotEmpty()
uuid: string;
}
//controller.ts
#Get(':uuid')
find(#Param() { uuid }: FindOneParams) {
return this.yourService.find(uuid);
}
//service.ts
find = (uuid: string): Promise<yourType> => ...

Related

NestJs/Swagger: How to add `additionalProperties: false` on an existing DTO class

Hello I am new to Nestjs and trying to implement additionalProperties: false on a DTO class that already has properties on it. I see that the additionalProperties key can be added inside #ApiProperty({ schema: ... { additionalProperties : false} }) but I want to add it like this:
class SomeResponseDto {
#ApiResponseProperty()
text: string;
#ApiResponseProperty()
id: string;
// maybe a new Decorator like this?
#ApiAdditionalProperties(false)
}
...so that only text and id is allowed in the SomeResponseDto. I want to avoid having to define every class as a schema object inside the controllers.
I should note that I'm using express-openapi-validator with nestjs/swagger, and do not want to use the class-validator/class-transformer plugins, so that I can validate responses as well as requests by using just nestjs/swagger decorators.
I have also tried this:
#ApiResponse({
status: 200,
description: 'success',
schema: {
oneOf: [
{
$ref: getSchemaPath(SomeResponseDto),
// additionalProperties: false, <-- this gets ignored
},
],
// additionalProperties: false, <-- this throws OpenApi invalid response errors
},
Is there any easy way to add additionalProperties: false on an existing DTO class?
Here is a workaround: Post this code inside the bootstrap() method of the application
const schemas = document?.components?.schemas;
Object.keys(schemas).forEach((item) => {
if (schemas[item]['properties']?.allowAdditional) {
schemas[item]['additionalProperties'] = true;
} else {
schemas[item]['additionalProperties'] = false;
}
});
This code above will set additionalProperties to false by default.
If for some reason you have a DTO class that you want to allow additionalProperties: true, then inside your DTO Class, add the following decorator and property:
export class SomeResponseDTO {
#ApiPropertyOptional()
allowAdditional?: boolean;
#ApiResponseProperty()
text: string;
#ApiResponseProperty()
id: string;
}
This is a simple solution for true/false case, but can be modified as needed to handle other use cases.
I hope this helps someone!

Nestjs custom class-validator decorator doesn't get the value from param

I have created a Custom ValidatorConstraint in Nestjs from class-validator, just to create my own decorator and apply later to DTO classes for validations.
Imagine this route.
foo/:client
after request it, I just want to check that client contains some pattern
client --> XXX123 ✘
client --> ZZZ123 ✔
I am struggling with it and although I saw some examples, it is still not very clear why it fails.
main.ts
app.useGlobalPipes(new ValidationPipe());
useContainer(app.select(AppModule), { fallbackOnErrors: true });
app.module.ts
providers: [..., IsValidClientConstraint],
app.controller.ts
#Get(':client')
getHello(#Param('client') client: ClientDTO): string {
custom.validator.ts
import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
import { Injectable } from '#nestjs/common';
#ValidatorConstraint({ async: false })
#Injectable()
export class IsValidClientConstraint implements ValidatorConstraintInterface {
validate(client: any, args: ValidationArguments) {
console.log(client)
return client.includes('ZZZ');
}
}
export function IsValidClient(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: IsValidClientConstraint,
});
};
}
client.dto.ts
export class ClientDTO {
#IsValidClient({ message: 'blabla' })
client: string;
}
However doing a request with -> foo/XXX344
ERROR [ExceptionsHandler] Cannot read properties of undefined
So it is not receiving the value of the client itself
What am I missing there?
I with leave the repo here
https://github.com/ackuser/nestjs-sample-custom-validator
Thanks,
I appreciate any help
You don't have to pass parameter name to #Param decorator when you want to use class-validator to validate params, So change it to #Param() params: ClientDTO instead.
Use custom pipes if you want to validate each parameter one by one. because the DTO method you used turns all parameters (not just :client) into a single data class.
Also in IsValidClientConstraint check if client is defined before using it.

nestjs/crud Passing Filter conditions from a controller to a service but CrudRequest type

I am using nestjsx/crud to control my API endpoints, and I would like to call another service from an existing controller as I would like to receive a set of tags back which I will then do more processing on.
The controller that will return me back the tags:
#Crud({
model: {
type: Test,
},
})
#Controller("test")
export class TestController implements CrudController<Test> {
constructor(public service: TestService) {}
get base(): CrudController<Test> {
return this;
}
#Override()
getMany(
#ParsedRequest() req: CrudRequest,
) {
return this.base.getManyBase(req);
}
}
I then have this controller which calls the getMany endpoint and then does some work with the results.
#Get('gettest')
getTest( #ParsedRequest() req: CrudRequest ) {
const results = this.testService.getMany( {
filter: [ { field: 'name', operator: 'eq', value: 'Justin' } ],
});
//
// Do more processing with the results
//
}
The issue I am experiencing is passing the filter parameter into the getMany as it is of type CrudRequest, so I am getting this message:
Object literal may only specify known properties, and 'filter' does not exist in type 'CrudRequest'
I have tried a number of different options and this was a project using nestjsx/crud 2.x so have moved it to latest but cannot find any material on the best way of passing filter information between services.
Does anyone know what the best way of doing this is?
Many thanks

Wrong data from client passes GraphQL validation

I've made simple CRUD app with React and Apollo client on NestJS server with GraphQL API.
I have this simple Mutations:
schema.gql:
type Mutation {
createUser(input: CreateUserInput!): User! // CreateUserInput type you can see in user.input.ts below
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): User!
}
user.input.ts:
import { InputType, Field } from "#nestjs/graphql";
import { EmailScalar } from "../email.scalar-type";
#InputType()
export class CreateUserInput {
// EmailScalar is a custom Scalar GraphQL Type that i took from the internet and it worked well
#Field(() => EmailScalar)
readonly email: string;
#Field()
readonly name: string;
}
"EmailScalar" type checks if "email" input has *#*.* format basically
And when i make createUser Query to GraphQL API like this:
It cannot pass validation
(because Email type works fine)
But when Query sent from client - it passes validation:
NestJS server log (from code below)
users.resolver.ts:
#Mutation(() => User)
async createUser(#Args('input') input: CreateUserInput) { // Type from user.input.ts
Logger.log(input); // log from screenshot, so if it's here it passed validation
return this.usersService.create(input); // usersService makes requests to MongoDB
}
And it gets into MongoDB
Here is client side part:
App.tsx:
...
// CreateUserInput class is not imported to App.tsx (it is at server part) but it seems to be fine with it
const ADD_USER = gql`
mutation AddMutation($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
`
function App(props: any) {
const { loading, error, data } = useQuery(GET_USERS);
const [addUser] = useMutation(
ADD_USER,
{
update: (cache: any, { data: { createUser } }: any) => {
const { users } = cache.readQuery({ query: GET_USERS });
cache.writeQuery({
query: GET_USERS,
data: {
users: [createUser, ...users],
},
})
}
}
);
...
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return <UserTable users={data.users} addUser={addUser} updateUser={updateUser} deleteUser={deleteUser} />;
}
Can someone please explain to me, how does client Query passes validation and what have i done wrong?
Even two empty strings can pass through.
Never worked with NestJS, Apollo, React or GraphQL before, so I'm kinda lost.
For full code:
https://github.com/N238635/nest-react-crud-test
This is how your custom scalar's methods are defined:
parseValue(value: string): string {
return value;
}
serialize(value: string): string {
return value;
}
parseLiteral(ast: ValueNode): string {
if (ast.kind !== Kind.STRING) {
throw new GraphQLError('Query error: Can only parse strings got a: ' + ast.kind, [ast]);
}
// Regex taken from: http://stackoverflow.com/a/46181/761555
var re = /^([\w-]+(?:\.[\w-]+)*)#((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if (!re.test(ast.value)) {
throw new GraphQLError('Query error: Not a valid Email', [ast]);
}
return ast.value;
}
parseLiteral is called when parsing literal values inside the query (i.e. literal strings wrapped in double quotes). parseValue is called when parsing variable values. When your client sends the query, it sends the value as a variable, not as a literal value. So parseValue is used instead of parseLiteral. But your parseValue does not do any kind of validation -- you just return the value as-is. You need to implement the validation logic in both methods.
It would also be a good idea to implement the serialize method so that your scalar can be used for both input and response validation.

Angular: TypeError: Cannot read property 'map' of undefined

I'm using MEAN Stack to build a grading app. I'm trying to get a list of items from the database into my Angular component via a service code but i keep getting core.js:14597 ERROR TypeError: Cannot read property 'map' of undefined.
//scoring.service.js
import { Scoring } from './scoring.model';
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { HttpClient } from '#angular/common/http';
import { Router } from '#angular/router';
#Injectable({providedIn: 'root'})
export class ScoringService {
private scoring: Scoring[] = [];
private updatedScores = new Subject<Scoring[]>();
constructor(private http: HttpClient, private router: Router){}
getScoring() {
this.http.get<{message: string, scores: any}>('http://localhost:3000/api/scoring')
.pipe(map((scoringData) => {
return scoringData.scores.map(score => {
status = score.IsActive ? 'checked' : 'unchecked';
return {
id: score._id,
Criteria: score.Criteria,
MaxPoints: score.MaxPoints,
IsActive: status,
DateCreated: score.DateCreated,
DateModified: score.DateModified
};
});
}))
.subscribe((transformedScores) => {
this.scoring = transformedScores;
this.updatedScores.next([...this.scoring]);
});
}
}
The code is supposed to list the items and map a boolean field from true or false to checked or unchecked respectively. But nothing is being listed at all. The error i'm getting is "Cannot read property 'map' of undefined." And I've used the same set of code in another component to list items without getting the error. Please, help me here, I will appreciate. Thanks.
Put some breakpoints in the pipe(map(scoringData)) part and see what you get.
The exception is throwed because you actually assume that "scoringData" returning an object formated like this :
{"scores": [ << an array of something >> ]}
But if scoringData is null, you tried to used the .map function to an undefined result.
Quick fix can be :
.pipe(map((scoringData) => {
return (scoringData && scoringData.scores || []).map(score => {
status = score.IsActive ? 'checked' : 'unchecked';
return {
id: score._id,
Criteria: score.Criteria,
MaxPoints: score.MaxPoints,
IsActive: status,
DateCreated: score.DateCreated,
DateModified: score.DateModified
};
});
}))
Better way consist to filter your results before using the map in your pipe.
It depend on which library of RxJS you are using, but normaly you can do something like that :
.pipe(filter(scoringData => !!scoringData && !!scoringData.scores && scoringData.scores.length), map((scoringData)
And the import of the filter operator should be the same as map operator :
import { filter } from 'rxjs/operators';

Resources