Nest-Connect API using NestJs, PassportJs, and Prisma

WHAT TO KNOW - Sep 7 - - Dev Community

Building a Secure NestJS API with PassportJS and Prisma: A Comprehensive Guide

Introduction

In the ever-evolving world of web development, building robust and secure APIs is paramount. NestJS, a progressive Node.js framework, provides a structured and efficient approach to API development. Combining this with PassportJS, a powerful authentication middleware, and Prisma, an ORM for modern database interaction, allows us to create secure, scalable, and maintainable APIs.

This comprehensive guide delves into the intricacies of building a NestJS API with PassportJS and Prisma, providing a step-by-step walkthrough and in-depth explanations of the core concepts.

Why Choose NestJS, PassportJS, and Prisma?

NestJS, inspired by Angular, promotes a modular and scalable architecture. It leverages TypeScript for robust type safety and provides a rich ecosystem of plugins and libraries.

PassportJS, a versatile middleware, simplifies authentication by offering a wide range of strategies for integrating with various providers like Google, Facebook, and OAuth. It handles user authentication and authorization, ensuring only authorized users can access protected resources.

Prisma, a modern ORM, simplifies database interactions by providing an intuitive and type-safe interface. It generates type-safe database clients and simplifies common tasks like querying, creating, updating, and deleting data.

Setting Up the Project

1. Project Initialization:

npx create-nest-app my-nest-api
cd my-nest-api
Enter fullscreen mode Exit fullscreen mode

2. Installing Dependencies:

npm install @nestjs/passport passport passport-local passport-jwt prisma @prisma/client --save
Enter fullscreen mode Exit fullscreen mode

Implementing Authentication with PassportJS

1. Defining Authentication Strategies:

We'll use PassportJS to handle authentication. Let's implement a Local Strategy for username/password authentication:

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({
      usernameField: 'email',
      passwordField: 'password',
    });
  }

  async validate(email: string, password: string): Promise
<any>
 {
    const user = await this.authService.validateUser(email, password);
    if (!user) {
      throw new Error('Invalid credentials');
    }
    return user;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Configuring PassportJS:

We need to configure PassportJS within our NestJS application:

import { Injectable } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Injectable()
export class AuthModule {
  static register(config: ConfigService) {
    return {
      imports: [
        PassportModule.register({ defaultStrategy: 'jwt' }),
        JwtModule.register({
          secret: config.get('JWT_SECRET'),
          signOptions: { expiresIn: '60s' },
        }),
        ConfigModule,
      ],
      providers: [LocalStrategy, JwtStrategy],
      exports: [PassportModule, JwtModule],
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Implementing Authentication Functionality:

Within our AuthService, we'll define methods for user registration, login, and validation:

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, UserDocument } from './schemas/user.schema';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(
    @InjectModel(User.name) private userModel: Model
 <userdocument>
  ,
    private jwtService: JwtService,
  ) {}

  async register(createUserDto: CreateUserDto): Promise
  <user>
   {
    const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
    const user = new this.userModel({
      ...createUserDto,
      password: hashedPassword,
    });
    return user.save();
  }

  async validateUser(email: string, password: string): Promise
   <user null="" |="">
    {
    const user = await this.userModel.findOne({ email });
    if (user &amp;&amp; (await bcrypt.compare(password, user.password))) {
      const { password, ...result } = user.toObject();
      return result;
    }
    return null;
  }

  async login(user: User) {
    const payload = { sub: user.id };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Securing API Endpoints:

Using @UseGuards decorator, we can protect API endpoints with authentication:

import { Controller, Get, Post, Body, UseGuards, Req } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { CreateUserDto } from './dto/create-user.dto';
import { LocalAuthGuard } from './guards/local-auth.guard';
import { JwtAuthGuard } from './guards/jwt-auth.guard';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Post('register')
  async register(@Body() createUserDto: CreateUserDto): Promise
    <user>
     {
    return this.authService.register(createUserDto);
  }

  @UseGuards(LocalAuthGuard)
  @Post('login')
  async login(@Req() req): Promise&lt;{ access_token: string }&gt; {
    return this.authService.login(req.user);
  }

  @UseGuards(JwtAuthGuard)
  @Get('profile')
  getProfile(@Req() req) {
    return req.user;
  }
}
Enter fullscreen mode Exit fullscreen mode
 <br/>
 ### Working with Prisma and Databases
Enter fullscreen mode Exit fullscreen mode

1. Setting up Prisma:

npx prisma init
Enter fullscreen mode Exit fullscreen mode

This generates a prisma directory containing necessary files.

2. Defining Data Models:

Create a schema.prisma file to define your data models:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(auto()) @map("_id")
  email     String   @unique
  password  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
Enter fullscreen mode Exit fullscreen mode

3. Generating Prisma Client:

Run npx prisma generate to generate the Prisma Client.

4. Accessing Database Functionality:

Within your services, you can now use Prisma Client to interact with the database:

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class UserService {
  constructor(private prisma: PrismaService) {}

  async findUserById(id: number) {
    return this.prisma.user.findUnique({ where: { id } });
  }

  async createUser(data: CreateUserDto) {
    return this.prisma.user.create({ data });
  }
}
Enter fullscreen mode Exit fullscreen mode
 <br/>
 ### Building a Secure API Endpoint
Enter fullscreen mode Exit fullscreen mode

Let's build a simple API endpoint that fetches user information and requires authentication:

1. Creating the Controller:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { UserService } from '../user/user.service';

@Controller('users')
@UseGuards(JwtAuthGuard)
export class UserController {
  constructor(private userService: UserService) {}

  @Get()
  async findAll() {
    return this.userService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: string) {
    return this.userService.findOne(+id);
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Implementing the Service:

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class UserService {
  constructor(private prisma: PrismaService) {}

  async findAll() {
    return this.prisma.user.findMany();
  }

  async findOne(id: number) {
    return this.prisma.user.findUnique({ where: { id } });
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, the /users endpoint will only be accessible to authenticated users with valid JWT tokens.


### Conclusion

This comprehensive guide provided a deep dive into building a secure NestJS API with PassportJS and Prisma. We learned how to integrate PassportJS for authentication, define authentication strategies, and secure API endpoints. Prisma was introduced as a modern ORM for simplified database interactions.

Key Takeaways:

  • NestJS provides a structured and scalable framework for API development.
  • PassportJS empowers developers with versatile authentication strategies, enhancing API security.
  • Prisma simplifies database interactions with its intuitive and type-safe interface.

Best Practices:

  • Modularize code: Organize your application into modules for better maintainability.
  • Use strong passwords and encryption: Secure user data with strong password hashing techniques.
  • Implement proper authorization: Restrict access to resources based on user roles and permissions.
  • Validate user input: Prevent security vulnerabilities by validating user input against expected formats.
  • Follow security best practices: Keep your dependencies updated and follow security guidelines to mitigate potential risks.

By following these best practices and utilizing the power of NestJS, PassportJS, and Prisma, you can create secure, efficient, and scalable APIs for your applications.




. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player