I have an endpoint with no entry params:
async myendpoint(): Promise<any> {
const customer = await this.customerService.findOne(1);
if (customer) {
return await this.customerService.mapToDestination(customer);
}...
}
Then I have my method mapToDestination where I simply assign vars:
async mapToDestination(customer: Customer): Promise<DestinationDto> {
const destination: DestinationDto = {
lastname: customer.lastname,
firstname: customer.firstname,...
Finally, I have my DTO:
import {IsEmail, IsNotEmpty, IsOptional, IsNumber, IsBoolean, IsString, IsDate, MaxLength, Length, NotEquals} from 'class-validator';
import {ApiProperty} from '#nestjs/swagger';
export class DestinationDto {
#IsString()
#IsNotEmpty()
#MaxLength(32)
lastname: string;
#IsString()
#IsNotEmpty()
#MaxLength(20)
firstname: string; ...
I would like my DTO fields to be validated automatically following the decorators when I'm mapping it in my mapToDestination() method. I look through the web and the official documentation and I gave a try to Validators (ValidationPipe) but it does not seem to be my need as it validates the endpoint entry params.
Please, could you explain to me how to achieve this automatic validation? Thanks in advance.
I won't be "automatic" but you could instantiate your own instance of the validator from class validator and use it against the DTO in your service. Otherwise, it won't ever happen automatically because as you said, the ValidationPipe only works on the entry of the endpoint.
Example
Inside of mapToDestination so long as customer is an instance of DestinationDTO` you can have something like this:
#Injectable()
export class CustomerService {
async mapToDestination(customer: DestinationDTO) {
const errors = await validate(customer);
if (errors) {
throw new BadRequestException('Some Error Message');
}
...
}
...
}
Related
The Nestjs module system is great, but I'm struggling to figure out how to take full advantage of it in a Serverless setting.
I like the approach of writing my domain logic in *.service.ts files, while using *.controller.ts files to take care of non-business related tasks such as validating an HTTP request body and converting to a DTO before invoking methods in a service.
I found the section on Serverless in the nestjs docs and determined that for my specific use-case, I need to use the "standalone application feature".
I created a sample nestjs app here to illustrate my problem.
The sample app has a simple add() function to add two numbers. I use class-validator for validation on the AddDto class.
// add.dto.ts
import { IsNumber } from 'class-validator'
export class AddDto {
#IsNumber()
public a: number;
#IsNumber()
public b: number;
}
And then, via some Nestjs magic, I am able to get built-in validation using the AddDto inside my controller by doing the following:
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Use `ValidationPipe()` for auto-validation in controllers
app.useGlobalPipes(
new ValidationPipe({ transform: true })
)
await app.listen(3000);
}
// app.controller.ts
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Post('add')
add(#Body() dto: AddDto): number {
// Request body gets auto validated and converted
// to an instance of `AddDto`, sweet!
return this.appService.add(dto.a, dto.b);
}
}
// app.service.ts
#Injectable()
export class AppService {
add(a: number, b: number): number {
return a + b
}
}
So far, so good. The problem now arises when using this in AWS with a Lambda function, namely:
I want to re-use the business logic in app.service.ts
I want to re-use built in validation that happens when making an HTTP request to the app, such as in the example above.
I want to use the standalone app feature so I don't have to spin up an entire nest server in Lambda
The docs hint on this being a problem:
Be aware that NestFactory.createApplicationContext does not wrap controller methods with enhancers (guard, interceptors, etc.). For this, you must use the NestFactory.create method.
For example, I have a lambda that receives messages from AWS EventBridge. Here's a snippet from the sample app:
// standalone-app.ts
interface IAddCommand {
a: number;
b: number;
}
export const handler = async (
event: EventBridgeEvent<'AddCommand', IAddCommand>,
context: any
) => {
const appContext = await NestFactory.createApplicationContext(AppModule);
const appService = appContext.get(AppService);
const { a, b } = event.detail;
const sum = appService.add(a, b)
// do work on `sum`, like cache the result, etc...
return sum
};
// lambda-handler.js
const { handler } = require('./dist/standalone-app')
handler({
detail: {
a: "1", // is a string, should be a number
b: "2" // is a string, should be a number
}
})
.then(console.log) // <--- prints out "12" ("1" + "2") instead of "3" (1 + 2)
I don't get "free" validation of the event's payload in event.detail like I do with #Body() dto: AddDto when making a HTTP POST request to /add. Preferentially, the code would throw a validation error in the above example. Instead, I get an answer of "12" -- a false positive.
Hopefully, this illustrates the crux of my problem. I still want to validate the payload of the event before calling appService.add(a, b), but I don't want to write custom validation logic that already exists on the controller in app.controller.ts.
Ideas? Anyone else run into this before?
It occurred to me while writing this behemoth of a question that I can simply use class-validator and class-transformer in my Lambda handler.
import { validateOrReject } from 'class-validator'
import { plainToClass } from 'class-transformer'
import { AddDto } from 'src/dto/add.dto'
export const handler = async (event: any, context: any) => {
const appContext = await NestFactory.createApplicationContext(AppModule);
const appService = appContext.get(AppService);
const data = getPayloadFromEvent(event)
// Convert raw data to a DTO
const dto: AddDto = plainToClass(AddDto, data)
// Validate it!
await validateOrReject(dto)
const sum = appService.add(dto.a, dto.b)
// do work on `sum`...
}
It's not as "free" as using app.useGlobalPipes(new ValidationPipe()), but only involves a few extra lines of code.
It worked for me with the following lambda file for nestjs.
import { configure as serverlessExpress } from '#vendia/serverless-express';
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common';
let cachedServer;
export const handler = async (event, context) => {
if (!cachedServer) {
const nestApp = await NestFactory.create(AppModule);
await nestApp.useGlobalPipes(new ValidationPipe());
await nestApp.init();
cachedServer = serverlessExpress({
app: nestApp.getHttpAdapter().getInstance(),
});
}
return cachedServer(event, context);
};
I'm trying to follow Ben Awad's lireddit tutorial.
At the time he made the tutorial, there may have been different inferences about Field types.
I'm trying to add a Field to my relationship attribute (adding creator Field to a Post), so that I can then access creator attributes on that post record.
Ben does this as follows:
#Field()
#ManyToOne(() => User, (user) => user.posts)
creator: User;
That worked for him. When I try this, I get an error that says:
throw new errors_1.NoExplicitTypeError(prototype.constructor.name, propertyKey,
parameterIndex, argName);
NoExplicitTypeError: Unable to infer GraphQL type from TypeScript
reflection system. You need to provide explicit type for 'creator' of
'Post' class.
When I look at the GraphQL docs for how to provide an explicit type for creator, I can't find a similar example (simple enough for me to decipher a principle that I can apply).
I'm confused by the docs, because the have the following example:
Can anyone see what I need to do to ask for the field to be recognised as an object that I can read from?
#ObjectType()
class Rate {
#Field(type => Int)
value: number;
#Field()
date: Date;
user: User;
}
I think they use user: User the same way I use creator: User. Is there a reason that Field() can't have the same thing as ObjectType()?
I tried:
#Field(() => [User])
#ManyToOne(() => User, (user) => user.posts)
creator: User;
This doesn't give any errors (neither does the code the way Ben has it), until I get to the playground, in which case, I can't return the user data - so clearly it's wrong. It also isn't clear whether the array means the array of attributes on the user object, or an array of users (which would also be wrong). I can see from the GraphQL docs that it should be possible to define a field attribute as an object type, but I can't find an example showing how to do that.
I have seen this post, which looks like a similar problem, but I can't see from the suggested answers, how to apply those ideas to this problem.
I have seen this post, which has a similar problem, and is answered with a reference to an example that shows how to write resolvers that find relations, but my resolver already worked to find the creatorId, so I think maybe I'm not looking in the right place for an answer.
In my post resolver, I have:
import {
Resolver,
Query,
Arg,
Mutation,
InputType,
Field,
Ctx,
UseMiddleware,
Int,
FieldResolver,
Root,
ObjectType,
} from "type-graphql";
import { Post } from "../entities/Post";
import { MyContext } from "../types";
import { isAuth } from "../middleware/isAuth";
import { getConnection } from "typeorm";
#InputType()
class PostInput {
#Field()
title: string;
#Field()
text: string;
}
#ObjectType()
class PaginatedPosts {
#Field(() => [Post])
posts: Post[];
#Field()
hasMore: boolean;
}
#Resolver(Post)
export class PostResolver {
#FieldResolver(() => String)
textSnippet(#Root() post: Post) {
return post.text.slice(0, 50);
}
#Query(() => PaginatedPosts)
async posts(
#Arg("limit", () => Int) limit: number,
#Arg("cursor", () => String, { nullable: true }) cursor: string | null
): Promise<PaginatedPosts> {
// 20 -> 21
const realLimit = Math.min(50, limit);
const reaLimitPlusOne = realLimit + 1;
const qb = getConnection()
.getRepository(Post)
.createQueryBuilder("p")
.orderBy('"createdAt"', "DESC")
.take(reaLimitPlusOne);
if (cursor) {
qb.where('"createdAt" < :cursor', {
cursor: new Date(parseInt(cursor)),
});
}
const posts = await qb.getMany();
return {
posts: posts.slice(0, realLimit),
hasMore: posts.length === reaLimitPlusOne,
};
}
#Query(() => Post, { nullable: true })
post(#Arg("id") id: number): Promise<Post | undefined> {
return Post.findOne(id);
}
#Mutation(() => Post)
#UseMiddleware(isAuth)
async createPost(
#Arg("input") input: PostInput,
#Ctx() { req }: MyContext
): Promise<Post> {
return Post.create({
...input,
creatorId: req.session.userId,
}).save();
}
#Mutation(() => Post, { nullable: true })
async updatePost(
#Arg("id") id: number,
#Arg("title", () => String, { nullable: true }) title: string
): Promise<Post | null> {
const post = await Post.findOne(id);
if (!post) {
return null;
}
if (typeof title !== "undefined") {
await Post.update({ id }, { title });
}
return post;
}
#Mutation(() => Boolean)
async deletePost(#Arg("id") id: number): Promise<boolean> {
await Post.delete(id);
return true;
}
}
First of all, creator should be of a User type, and not a list of users, i.e.
#Field(() => User)
#ManyToOne(() => User, (user) => user.posts)
creator: User;
When you are retrieving a post, you should include a relation in your query, so the User entity is also loaded:
#Query(() => Post, { nullable: true })
post(#Arg("id") id: number): Promise<Post | undefined> {
return Post.findOne(id, { relations: "creator" });
}
Also, when you're using query builder to fetch posts, you should add User entities:
const qb = getConnection()
.getRepository(Post)
.createQueryBuilder("p")
.leftJoinAndSelect("p.creator", "p_creator")
.orderBy('"createdAt"', "DESC")
.take(reaLimitPlusOne);
Bonus note:
There's a common problem of over-fetching the data in GraphQL, so queries can become slow with time.
In that manner, you could also consider moving the creator field to FieldResolver, so it's retrieved from the database only if it's requested. In case you do that, one other good practice with ManyToOne relations is to use a dataloader, so if you, for example, load 10 posts from the same creator, you'll end up with only one fetching operation of that creator instead of 10 requests to the database. There's a great tutorial and explanation provided by Ben Awad too: https://www.youtube.com/watch?v=uCbFMZYQbxE.
This isn't necessary for this tutorial in particular, but it's a must-know if you're building some serious app.
I want to create a GraphQL API using NestJs. As far as I understood I won't be throwing HTTP exceptions for invalid requests anymore. Therefore I think I have to create my own "error codes" I can send back to the client. So given this basic example
#ObjectType()
export class ErrorResponse {
#Field()
message: string;
}
I have a service function to return a user by its ID and I extended the return type to return an error object if the request was invalid.
public async getUserById(id: number): Promise<ErrorResponse | User> {
const user: User = await this.usersRepository.findOne(id);
if (!user) {
const errorResponse: ErrorResponse = new ErrorResponse();
errorResponse.message = `User with ID ${id} does not exist`;
return errorResponse;
}
return user;
}
The resolver originally was something like
#Query(() => User)
public async user(#Args('id') id: number): Promise<ErrorResponse | User> {
return this.usersService.getUserById(id);
}
but as mentioned above it's also possible to return a ErrorResponse if the id does not exist. How can I design the Query decorator to provide multiple return types?
#Query(() => ErrorResponse | User)
won't do the trick and shows up with this error
The left-hand side of an arithmetic operation must be of type 'any',
'number', 'bigint' or an enum type.ts(2362)
This is the solution that i came up for a similar situation.
GraphQL expects single return ObjectType.
First i created a common Object
#ObjectType()
export class MutationResult {
#Field({ nullable: true })
success?: boolean;
#Field({ nullable: true })
error?: boolean;
}
Then in the user module i created 2 objects types - User and UserResponse. On UserResponse i extened the common MutationResult Object
#ObjectType()
export class User {
#Field(type => ID)
id: string;
#Field()
name: string;
}
#ObjectType()
export class UserResponse extends MutationResult {
#Field()
result: User;
}
Now in query you can do this
mutation {
addUser(name: "Test") {
success,
error,
result {
name
}
}
}
If both ErrorResponse and User are an #ObjectType, you just need to "merge" them together using createUnionType.
https://docs.nestjs.com/graphql/unions-and-enums
Answer by Michal seems to be working, but the link is redirecting to some spam post. Below link is official documentation for nestjs:
https://docs.nestjs.com/graphql/unions-and-enums
I know this question gets asked frequently for the default passport AuthGuard('yourStrategy'),
but haven't found the answer for custom auth guards yet.
Why I use a custom auth guard? Because the default one and GraphQL seems to be unable to work together.
Since some update on GraphQL's side, the default AuthGuard cannot read the header any more.
I need to pass the user data, which I got from the bearer token, somehow to the resolvers.
How is passport doing this? How would you do this? I'm pretty new to nestJS and the lack of dockumentation and / or propper tutorials drives me crazy.
Relevant code:
auth.guard.ts
#Injectable()
export class AuthGuard implements CanActivate {
constructor(readonly jwtService: JwtService/*, readonly userService: UsersService*/) { }
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().request;
const Authorization = request.get('Authorization');
if (Authorization) {
const token = Authorization.replace('Bearer ', '');
const { userId, firstName } = this.jwtService.verify(token) as { userId: string; firstName: string } ;
return !!userId;
}
}
}
jwt.strategy.ts
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload) {
return {userId: payload.userId, firstName: payload.firstName};
}
}
auth.module.ts
#Module({
imports: [
forwardRef(() => UserModule) ,
PassportModule.register({
defaultStrategy: 'jwt'
}),
JwtModule.register({
secret: jwtConstants.secret,
signOptions: {expiresIn: 3600}
})],
providers: [AuthService, JwtStrategy, AuthResolver, AuthGuard],
exports: [AuthService, JwtModule, AuthGuard]
})
export class AuthModule {
}
example resolver
#UseGuards(AuthGuard)
#Resolver((of) => UserSchema)
export class UserResolver {
constructor(private readonly userService: UserService) {}
// ===========================================================================
// Queries
// ===========================================================================
#Query(() => UserDto, {description: 'Searchs for a user by a given id'})
async getUserById(#Args('id') id: string) {
/*
* Instead of passing the userID as an query argument, get it from the bearer token / auth guard!
*/
const result = await this.userService.findById(id);
if(result) return result;
return new NotFoundException('User not found!');
}
}
Thanks for help in advance! ;-)
Edit: In case you need to see more code, you could use my github repo: https://github.com/JensUweB/ExamAdmin-Backend
Never mind. I have found a solution to this myself. I found a workaround to get the passport AuthGuard back to work with GraphQL. And for the userId: use a custom User Decorator: github.com/nestjs/graphql/issues/48#issuecomment-420693225
I'm trying to mock only a small portion of the Contact type below. My resolvers return data from a REST endpoint for all fields in Contact except for test. For demo purposes, I want to be able to retain the server data for all other fields, but only mock the test field.
I have the following GraphQL schema defined:
const typeDefs = `
type Contact {
id: String,
first_name: String
last_name: String
middle_name: String
date_of_birth: String
test: String
}
type Query {
contacts: [Contact!]!
Contact(id: String!): Contact!
}
`;
I have the following mocks defined:
const mocks = {
Contact: () => ({
test: () => "This data is mocked!"
})
};
And the following resolvers defined:
const resolvers = {
Query: {
contacts: async (parent, args, { dataSources }) =>
dataSources.MYAPI.getAllContacts(),
Contact: async (parent, { id }, { dataSources }) =>
dataSources.MYAPI.getContact(id)
}
};
Then I initialize the server with:
const server = new ApolloServer({
typeDefs,
resolvers,
mocks,
dataSources: () => {
return {
MYAPI: new MYAPI()
};
},
mockEntireSchema: false
});
The above does not work. I added the mockEntireSchema:true configuration which prevented my server response from being overridden, but the test attribute still returns the default String mock of Hello World instead of my attempted mock of This data is mocked!.
I know the mock is set up correctly because I can remove the mockEntireSchema config and my mock data appears correctly.
Is this even possible or does the behavior of mockEntireSchema and mocks in general not support this?
According to the documentation, you want to keep mockEntireSchema as false and create a mocks object that has the components in it that you still WANT to mock. All other resolvers will be used as they exist. Any mocks that you HAVE defined, though, will be used, so the query resolvers that return Contact types will never be used, since you have defined Contact as a mock.
I now believe that this is actually a bug in Apollo https://github.com/apollographql/graphql-tools/issues/1114. When you use preserveResolvers: true or mockEntireSchema: false (they are the same), it will not overwrite existing resolvers, however, I filtered those resolvers out based on my mock configuration, so they are not loaded in the first place.
This makes the partial mocking work in principle. The bug however is that nested () => MockList(100) calls throw an error in the graphql package because graphql interprets the MockList object as "not an iterable". This doesn't happen with preserveResolvers: false
I haven't tried it but to me it seems like it should work (what you describe). But since it doesn't, a possible workaround would be to just add a field resolver for the test field:
const resolvers = {
Contact: {
test: () => "This data is not mocked but the effect is the same!"
},
Query: {
contacts: async (parent, args, { dataSources }) =>
dataSources.MYAPI.getAllContacts(),
Contact: async (parent, { id }, { dataSources }) =>
dataSources.MYAPI.getContact(id)
}
};