GraphQL allows for flexible and efficient data querying, but managing input validation and authentication within your GraphQL API requires specific strategies. NestJS provides powerful tools to handle both input validation and authentication seamlessly.
In this module, we will explore how to manage input validation using class-validator and implement authentication in your GraphQL API to ensure that only authorized users can access protected resources.
Table of Contents
- Input Validation in GraphQL
- Authentication in GraphQL
- Best Practices for Input Validation and Auth
- Conclusion
Input Validation in GraphQL
Using class-validator with DTOs
One of the key features of NestJS is its ability to handle validation through DTOs (Data Transfer Objects). DTOs allow you to structure the data expected from the client, and the class-validator library makes it easy to validate incoming data.
To start with input validation, you first need to install the necessary packages:
bashCopyEditnpm install class-validator class-transformer
Now, let’s create a DTO for creating a user, and add validation decorators using class-validator
.
tsCopyEdit// create-user.input.ts
import { InputType, Field } from '@nestjs/graphql';
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
@InputType()
export class CreateUserInput {
@Field()
@IsNotEmpty()
@MinLength(3)
name: string;
@Field()
@IsEmail()
email: string;
@Field()
@IsNotEmpty()
@MinLength(6)
password: string;
}
In this example:
@IsNotEmpty()
ensures that the field is not empty.@MinLength()
checks that the input is long enough.@IsEmail()
validates that the email is in the correct format.
Validating Input in Resolvers
Once you have created the DTO, you can use it in the resolver and validate the input before proceeding with any logic.
tsCopyEdit// user.resolver.ts
import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { CreateUserInput } from './create-user.input';
import { User } from './user.model';
import { UserService } from './user.service';
import { UsePipes, ValidationPipe } from '@nestjs/common';
@Resolver(of => User)
export class UserResolver {
constructor(private userService: UserService) {}
@Mutation(returns => User)
@UsePipes(new ValidationPipe())
async createUser(@Args('input') input: CreateUserInput): Promise<User> {
return this.userService.create(input);
}
}
In this example:
- The
@UsePipes()
decorator ensures that the input is validated using theValidationPipe
before the resolver logic is executed. - The
ValidationPipe
automatically checks if the input conforms to the rules defined in the DTO using class-validator.
Authentication in GraphQL
JWT Authentication Strategy
In many GraphQL applications, protecting certain queries and mutations is crucial. JSON Web Tokens (JWT) are widely used for user authentication in APIs. To handle JWT authentication in NestJS, we will create a custom authentication guard.
First, install the necessary packages for JWT authentication:
bashCopyEditnpm install @nestjs/jwt passport-jwt
Then, configure JWT authentication in your auth module:
tsCopyEdit// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private jwtService: JwtService) {}
async generateToken(userId: string): Promise<string> {
return this.jwtService.sign({ userId });
}
}
You can create a guard to protect your GraphQL resolvers:
tsCopyEdit// jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
The JwtAuthGuard
will check the request for a valid JWT and ensure that only authenticated users can access protected resources.
Protecting Resolvers with Guards
Once you have your authentication strategy and guard in place, you can protect GraphQL resolvers by using the @UseGuards()
decorator.
Here’s how you can protect a query and a mutation:
tsCopyEdit// user.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { User } from './user.model';
import { UserService } from './user.service';
import { UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@Resolver(of => User)
export class UserResolver {
constructor(private userService: UserService) {}
@Query(returns => User)
@UseGuards(JwtAuthGuard)
async getUser(@Args('id') id: string): Promise<User> {
return this.userService.findOne(id);
}
@Mutation(returns => User)
@UseGuards(JwtAuthGuard)
async createUser(@Args('input') input: CreateUserInput): Promise<User> {
return this.userService.create(input);
}
}
In this example:
- Both the
getUser
andcreateUser
operations are protected by theJwtAuthGuard
. This ensures that the user must be authenticated to access these operations.
Best Practices for Input Validation and Auth
- Use DTOs for Input Validation: Define and validate the input structure with DTOs to ensure that the data coming into your GraphQL API is in the expected format.
- Keep Guards Simple: Your guards should focus solely on authentication and authorization. Complex business logic should not reside in guards.
- Leverage NestJS’s Built-in Pipes and Validation: NestJS offers built-in
ValidationPipe
and class-validator integration that automates much of the work, ensuring that your data is properly validated before it’s processed. - Use Access Control: Ensure that sensitive queries and mutations are protected and that the correct permissions are enforced.
- Ensure Secure Token Handling: Use JWT tokens securely and ensure they are transmitted over secure channels (e.g., HTTPS).
Conclusion
In this module, we have covered how to handle input validation and authentication in GraphQL using NestJS. By using DTOs with class-validator and implementing JWT authentication, you can ensure that your GraphQL API is both secure and robust. Additionally, we saw how to protect resolvers using guards to enforce role-based access control and prevent unauthorized access.
With these techniques, you can create a secure and well-validated GraphQL API in NestJS that is ready to handle a variety of use cases, from simple data fetching to complex, authenticated workflows.