In this tutorial, we will create a secure backend application using NestJS, Prisma, and JWT-based authentication. Our application will include CRUD operations for managing books, with endpoints protected by JWT authentication.
Prerequisites
Before we start, ensure you have the following installed on your machine:
- Node.js and npm(Better to have a Lts version Installed)
- Nest CLI: Install globally using npm install -g @nestjs/cli
- PostgreSQL (or any other Prisma-supported database) running and accessible
Step 1: Create a New NestJS Project
First, create a new NestJS project using the Nest CLI:
nest new book-store
cd book-store
Step 2: Install Dependencies
Next, install the necessary dependencies for JWT authentication and Prisma:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt @prisma/client prisma
Step 3: Initialize Prisma
If you are using the docker image of Postgresql add the below lines in the docker-compose.yml.
version: '3.8'
services:
postgres:
container_name: postgres_container
image: postgres:13
ports:
- 5434:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 123
POSTGRES_DB: book-store
volumes:
- postgres_data:/var/lib/postgresql/data
Update your .env file with your database connection string.
DATABASE_URL="postgresql://postgres:123@localhost:5434/book-store?schema=public"
Initialize Prisma in your project and configure the database connection:
npx prisma init
Step 4: Configure Prisma Schema
Edit prisma/schema.prisma to include the User and Book models:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
firstName String?
lastName String?
password String
}
model Book {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
description String?
link String
userId Int
}
Run the Prisma migration to apply the schema to the database:
npx prisma migrate dev --name init
Generate the Prisma client:
npx prisma generate
Step 5: Set Up Authentication
Generate the Auth module, controller, and service:
nest generate module auth
nest generate controller auth
nest generate service auth
Configure the Auth module:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { PrismaService } from '../prisma.service';
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'secretKey',
signOptions: { expiresIn: '60m' },
}),
],
providers: [AuthService, JwtStrategy, PrismaService],
controllers: [AuthController],
})
export class AuthModule {}
Configure the auth.service.ts
Implement the AuthService with registration and login functionality:
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
private prisma: PrismaService
) {}
async validateUser(email: string, pass: string): Promise<any> {
const user = await this.prisma.user.findUnique({ where: { email } });
if (user && await bcrypt.compare(pass, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { email: user.email, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
async register(email: string, pass: string) {
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(pass, salt);
const user = await this.prisma.user.create({
data: {
email,
password: hashedPassword,
},
});
const { password, ...result } = user;
return result;
}
}
Configure the auth.controller.ts
Create endpoints for login and registration in AuthController:
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
async login(@Body() req) {
return this.authService.login(req);
}
@Post('register')
async register(@Body() req) {
return this.authService.register(req.email, req.password);
}
}
Configure the jwt.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET || 'secretKey',
});
}
async validate(payload: any) {
return { userId: payload.sub, email: payload.email };
}
}
Create the JWT authentication guard(jwt-auth.guard.ts):
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
Step 6: Set Up Prisma Service
Create a Prisma service(prisma.service.ts) to handle database interactions:
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
Step 7: Create Books Module
Generate the Books module, controller, and service:
nest generate module books
nest generate controller books
nest generate service books
Configure the Books module(books.module.ts):
import { Module } from '@nestjs/common';
import { BooksService } from './books.service';
import { BooksController } from './books.controller';
import { PrismaService } from '../prisma.service';
@Module({
providers: [BooksService, PrismaService],
controllers: [BooksController]
})
export class BooksModule {}
Implement the BooksService(books.service.ts):
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma.service';
import { Book } from '@prisma/client';
@Injectable()
export class BooksService {
constructor(private prisma: PrismaService) {}
async create(data: Omit<Book, 'id'>): Promise<Book> {
return this.prisma.book.create({ data });
}
async findAll(userId: number): Promise<Book[]> {
return this.prisma.book.findMany({ where: { userId } });
}
async findOne(id: number, userId: number): Promise<Book> {
return this.prisma.book.findFirst({ where: { id, userId } });
}
async update(id: number, data: Partial<Book>, userId: number): Promise<Book> {
return this.prisma.book.updateMany({
where: { id, userId },
data,
}).then((result) => result.count ? this.prisma.book.findUnique({ where: { id } }) : null);
}
async remove(id: number, userId: number): Promise<Book> {
return this.prisma.book.deleteMany({
where: { id, userId },
}).then((result) => result.count ? this.prisma.book.findUnique({ where: { id } }) : null);
}
}
Secure the BooksController with JWT authentication:
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request } from '@nestjs/common';
import { BooksService } from './books.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@Controller('books')
@UseGuards(JwtAuthGuard)
export class BooksController {
constructor(private readonly booksService: BooksService) {}
@Post()
create(@Body() createBookDto, @Request() req) {
return this.booksService.create({ ...createBookDto, userId: req.user.userId });
}
@Get()
findAll(@Request() req) {
return this.booksService.findAll(req.user.userId);
}
@Get(':id')
findOne(@Param('id') id: string, @Request() req) {
return this.booksService.findOne(+id, req.user.userId);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateBookDto, @Request() req) {
return this.booksService.update(+id, updateBookDto, req.user.userId);
}
@Delete(':id')
remove(@Param('id') id: string, @Request() req) {
return this.booksService.remove(+id, req.user.userId);
}
}
Step 8: Integrate Everything
Ensure all modules are correctly imported in the main app module:
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { BooksModule } from './books/books.module';
@Module({
imports: [AuthModule, BooksModule],
})
export class AppModule {}
Running the Application
npm run start:dev
Conclusion
In this tutorial, we created a NestJS application with Prisma for database interaction and JWT for securing the API endpoints. We covered setting up the Prisma schema, creating modules for authentication and books, and securing the endpoints using JWT guards. You now have a secure NestJS backend with JWT-based authentication and CRUD operations for books.
Top comments (0)