Backend Complete Course | NodeJS, ExpressJS, JWT, PostgreSQL, Prisma...
By PedroTech
Key Concepts
- Node.js: JavaScript runtime environment for server-side development.
- Express.js: A fast, unopinionated, minimalist web framework for Node.js.
- REST API: Representational State Transfer Application Programming Interface, a set of architectural constraints for web services.
- Routing: Defining how an application responds to client requests to specific endpoints.
- Middleware: Functions that have access to the request and response objects and can modify them, end the request-response cycle, or call the next middleware.
- Authentication: Verifying the identity of a user (e.g., login, registration).
- Authorization: Determining if an authenticated user has permission to perform a specific action.
- Validation: Ensuring incoming data conforms to expected types and formats.
- Prisma: An open-source ORM (Object-Relational Mapper) that simplifies database interactions.
- PostgreSQL: A powerful, open-source object-relational database system.
- Neon: A serverless PostgreSQL provider.
- Requestly: An API client for testing and debugging API requests (alternative to Postman).
- Nodemon: A utility that monitors for changes in source code and automatically restarts the server.
- JSON Web Tokens (JWT): A compact, URL-safe means of representing claims to be transferred between two parties, used for stateless authentication.
- Bcrypt.js: A library for hashing passwords securely.
- ZOD: A TypeScript-first schema declaration and validation library.
- VPS (Virtual Private Server): A virtual machine sold as a service by an Internet hosting service.
- Hostinger: A web hosting provider offering VPS solutions.
- PM2: A production process manager for Node.js applications with a built-in load balancer.
- Nginx: A web server that can also be used as a reverse proxy, load balancer, and HTTP cache.
- Certbot: A free, open-source software tool for automatically using Let's Encrypt certificates on manually-configured HTTPS web servers.
- HTTP Status Codes: Standardized codes indicating the result of an HTTP request (e.g., 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found, 500 Internal Server Error).
- Environment Variables (
.env): Variables external to the code, used for sensitive information or configuration that changes between environments. - HTTP-only Cookies: Cookies that cannot be accessed by client-side scripts, enhancing security against XSS attacks.
- CSRF (Cross-Site Request Forgery): An attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated.
Introduction and Project Overview
The video addresses common challenges beginners face in backend development, such as overwhelming complexity and ineffective learning methods (copy-pasting code). It aims to teach how to build a real and secure REST API using Node.js and Express by focusing on fundamental concepts like routing, middleware, validation, and authentication. The project built throughout the tutorial is a Movie Watch List REST API, tested in real-time using Requestly and deployed with Hostinger. By the end, viewers will understand server structuring, route creation, database connection, authentication/authorization, request validation, and deployment with clean code and best practices.
Project Setup and Core Dependencies
- Project Initialization:
- A new folder,
backend-course, is created and opened in VS Code. npm init -yis run to create apackage.jsonfile, which serves as the blueprint for the Node.js project, listing dependencies, scripts, and basic project info.
- A new folder,
- Project Structure:
- A
srcfolder is created to contain all source material. server.jsis designated as the entry point for the API, responsible for creating and starting the server.
- A
- Key Package Installation:
npm install express: Express.js is installed as the framework for building the API. Node.js is the runtime, while Express simplifies API development.npm install nodemon --save-dev: Nodemon is installed as a development dependency. It automatically restarts the server upon file changes, eliminating manual restarts during development.
- Server Initialization (
server.js):import express from 'express';(after configuringpackage.jsonfor ES Modules).const app = express();creates an Express application instance.const PORT = process.env.PORT || 5001;defines the port, defaulting to 5001.const server = app.listen(PORT, '0.0.0.0', () => console.log(Server running on port ${PORT}));starts the server, listening on the specified port and0.0.0.0to accept traffic from any network interface.
- Running the Server with Nodemon:
- A
devscript is added topackage.json:"dev": "nodemon src/server.js". npm run devstarts the server, leveraging Nodemon for automatic restarts.
- A
API Endpoints and Routing Fundamentals
- HTTP Methods: The core HTTP methods for REST APIs are explained:
- GET: Retrieve data.
- POST: Create data.
- PUT: Update existing data (full replacement).
- DELETE: Remove data.
- PATCH: Partially update data (mentioned as another option).
- First API Endpoint (
app.get):app.get('/hello', (req, res) => { res.json({ message: 'Hello World' }); });demonstrates a basic GET request.req(request) contains incoming data from the client.res(response) is used to send data back to the client, typically as JSON.
- Testing with Requestly:
- Requestly is introduced as a lightweight, free, and locally controlled API client (preferred over Postman).
- Workspaces and Collections are created to organize requests.
- Variables (e.g.,
baseURLforhttp://localhost:5001) are used for reusability. - A GET request to
{{baseURL}}/hellois made, demonstrating a successful200 OKresponse.
- Organizing Endpoints with Express Routers:
- The need to categorize endpoints (e.g., authentication, movies, users, watch list) is highlighted to avoid a monolithic
server.js. - A
routesfolder is created. movieRoutes.jsis created to define movie-related endpoints.import { Router } from 'express'; const router = Router();initializes an Express router.router.get('/hello', (req, res) => res.json({ message: 'hello' }));defines an endpoint within the router.export default router;exports the router.- In
server.js, the router is imported and applied usingapp.use('/movies', movieRoutes);. This prefixes all routes inmovieRouteswith/movies. - Testing confirms
{{baseURL}}/movies/helloworks, while{{baseURL}}/hellono longer does. - Multiple HTTP methods (GET, POST, PUT, DELETE) can share the same path if their HTTP method differs.
- The need to categorize endpoints (e.g., authentication, movies, users, watch list) is highlighted to avoid a monolithic
Database Integration with Prisma and Neon
- PostgreSQL Database Setup (Neon):
- Neon is recommended for a free, hosted PostgreSQL database.
- A new project (
movie-watch-list-API) is created on Neon, selecting AWS US West as the region. - The connection string (database URL) is copied from Neon.
- ORM Introduction (Prisma):
- ORM (Object-Relational Mapper) is explained as a tool to interact with databases using JavaScript objects, avoiding raw SQL queries and enhancing security.
- Prisma is chosen as a popular, database-agnostic ORM.
- Prisma Setup:
npx prisma initinitializes Prisma in the project, creating aprismafolder andschema.prismafile.npm install prisma --save-devandnpm install @prisma/clientinstall the necessary Prisma packages.- The Prisma VS Code extension is recommended for syntax highlighting.
- Environment Variables (
.env):- A
.envfile is created to store sensitive information like theDATABASE_URL. - The copied Neon connection string is pasted into
.envasDATABASE_URL. npm install dotenvis installed to load environment variables.import 'dotenv/config';is added at the top ofserver.jsto load.envvariables.
- A
- Database Configuration (
config/db.js):- A
configfolder is created withdb.js. import { PrismaClient } from '@prisma/client';const prisma = new PrismaClient({ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'] });creates a Prisma client instance, configuring logging based on theNODE_ENV(development or production).connectDBanddisconnectDBasync functions are created usingprisma.$connect()andprisma.$disconnect(), withtry-catchblocks for error handling andprocess.exit(1)on connection failure.- These functions and the
prismainstance are exported.
- A
- Integrating DB Connection in
server.js:connectDBanddisconnectDBare imported and called inserver.js.- Robust error handling for
unhandledRejection,uncaughtException, andSIGTERMevents is added to gracefully disconnect the database and shut down the server, preventing memory leaks.
- Defining Database Schema (
prisma/schema.prisma):- Models are defined for
User,Movie, andWatchListItem. - User Model:
id(UUID, default),name(string),email(string, unique),password(string),createdAt(DateTime, defaultnow()). - Movie Model:
id(UUID),title(string),overview(string, optional),year(int),genres(string array, default empty),runtime(int, optional),poster(string, optional),createdBy(string, referencesUser.id),createdAt(DateTime). - WatchListItem Model:
id(UUID),userId(string, referencesUser.id),movieId(string, referencesMovie.id),status(customWatchListStatusenum, defaultplanned),rating(int, optional),notes(string, optional),createdAt(DateTime),updatedAt(DateTime, defaultnow(),onUpdatenow()). - Enum
WatchListStatus:planned,watching,completed,dropped. - Relationships:
@@unique([userId, movieId])is added toWatchListItemto ensure a user can only add a specific movie once.@@relationdecorators are used to define foreign key relationships andonDelete: Cascadefor referential integrity. - The
generator clientinschema.prismais modified to removeoutputand changeprovidertojsfor better type generation.
- Models are defined for
- Applying Schema Changes:
npx prisma migrate dev --name add_users_table(and subsequent migrations for other tables) applies schema changes to the Neon database.npx prisma generategenerates Prisma Client types for autocompletion.- Verification on Neon dashboard confirms table creation.
Authentication and Authorization
- Controller Pattern:
- A
controllersfolder is created. authController.jswill contain the logic for authentication routes.- Routes (
authRoutes.js) will import and call controller functions, keeping route definitions clean.
- A
- Register User (
POST /auth/register):- Body Parsing Middleware:
app.use(express.json());andapp.use(express.urlencoded({ extended: true }));are added toserver.jsto parse JSON and URL-encoded data from request bodies. - Controller Logic (
authController.js):- Destructures
name,email,passwordfromreq.body. - Check for existing user:
prisma.user.findUnique({ where: { email } }). IfuserExists, returns400 Bad Requestwith "User already exists with this email." - Password Hashing:
npm install bcryptjs.import bcrypt from 'bcryptjs';.const salt = await bcrypt.genSalt(10);generates a salt.const hashedPassword = await bcrypt.hash(password, salt);hashes the password.
- Create User:
prisma.user.create({ data: { name, email, password: hashedPassword } }). - Returns
201 Createdwith success status and user details (ID, name, email).
- Destructures
- Body Parsing Middleware:
- Login User (
POST /auth/login):- Controller Logic (
authController.js):- Destructures
email,passwordfromreq.body. - Find user:
prisma.user.findUnique({ where: { email } }). If no user, returns401 Unauthorizedwith "Invalid email or password." - Verify password:
const isPasswordValid = await bcrypt.compare(password, user.password);. If not valid, returns401 Unauthorizedwith "Invalid email or password." - Returns
200 OKwith success status and user details.
- Destructures
- Controller Logic (
- JSON Web Tokens (JWT) for Stateless Authentication:
- JWT Introduction: Small, signed tokens for verifying credentials without server-side session storage.
npm install jsonwebtoken.utils/generateToken.js:import jwt from 'jsonwebtoken';JWT_SECRETandJWT_EXPIRES_INenvironment variables are added to.env(secret generated usingopenssl rand -base64 32, expiry e.g., "7d").generateToken(userId, res)function:- Creates a
payloadobject{ id: userId }. const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '7d' });signs the token.- Sets HTTP-only cookie:
res.cookie('jwt', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000 });httpOnly: true: Prevents client-side JavaScript access (XSS protection).secure: true(in production): Sends cookie only over HTTPS.sameSite: 'strict': Protects against CSRF attacks.maxAge: Sets cookie expiry in milliseconds.
- Returns the
token.
- Creates a
- The
generateTokenfunction is called in bothregisterandlogincontrollers, passing theuserIdandresobject.
- Logout User (
POST /auth/logout):- Controller Logic (
authController.js):res.cookie('jwt', '', { httpOnly: true, expires: new Date(0) });clears the JWT cookie by setting an empty value and an immediate expiry.- Returns
200 OKwith "Logged out successfully."
- Controller Logic (
- Authentication Middleware (
middleware/authMiddleware.js):- A
middlewarefolder is created withauthMiddleware.js. protect(orauthMiddleware) async function:(req, res, next)- Extract Token:
- Checks
req.headers.authorizationfor a "Bearer" token. If found, splits and extracts the token. - Else, checks
req.cookies.jwt.
- Checks
- If no token, returns
401 Unauthorized("Not authorized, no token provided"). - Verify Token:
const decoded = jwt.verify(token, process.env.JWT_SECRET); - Find User:
const user = await prisma.user.findUnique({ where: { id: decoded.id } }); - If no user (e.g., deleted account), returns
401 Unauthorized("User no longer exists"). - Attach User to Request:
req.user = user;This makes the authenticated user object available in subsequent route handlers. next();calls the next middleware or route handler.- Catches any verification errors and returns
401 Unauthorized("Not authorized, token failed").
- Extract Token:
- Applying Middleware: In
watchListRoutes.js,router.use(authMiddleware);applies the middleware to all routes in that router. Alternatively, it can be applied to specific routes:router.post('/', authMiddleware, addToListController);. - The
userIdinaddToListControlleris now accessed viareq.user.idinstead ofreq.body.userId, ensuring the request is made by the authenticated user.
- A
- Seed File for Mock Data (
prisma/seed.js):- A
seed.jsfile is created to populate the database with mock movie data. - It uses
PrismaClientto create a new user (Tarantino) and then 10 movie entries, associating them with Tarantino's ID. - A
seed-moviesscript is added topackage.json:"seed-movies": "node prisma/seed.js". npm run seed-moviesexecutes the script.
- A
Request Validation with ZOD
- Validation Introduction: The importance of validating incoming request bodies to ensure data integrity and prevent errors is emphasized.
- ZOD Installation:
npm install zod. - Validators Folder: A
validatorsfolder is created, withwatchListValidators.jsfor watch list related schemas. - Defining Schemas (
watchListValidators.js):import { z } from 'zod';export const addToListSchema = z.object({ ... });defines a schema for theadd to watch listrequest body.- Schema Fields:
movieId: z.string().uuid(): Must be a string and a valid UUID.status: z.enum(['planned', 'watching', 'completed', 'dropped']).optional().refine(...): Must be one of the enum values, is optional, and includes a custom error message if invalid.rating: z.coerce.number().int().min(1).max(10).optional().refine(...): Coerces string numbers to actual numbers, must be an integer between 1 and 10, is optional, with custom error messages.notes: z.string().optional(): Must be a string, is optional.
- Validation Middleware (
middleware/validateRequest.js):export const validateRequest = (schema) => (req, res, next) => { ... };creates a higher-order middleware function that takes a ZOD schema.const result = schema.safeParse(req.body);attempts to parse and validate the request body against the schema.safeParsereturns an object withsuccess(boolean) anddataorerror.- If
!result.success, it formats the ZOD errors usingresult.error.format()._errors.flat()to get a clean array of error messages. - Returns
400 Bad Requestwith a JSON containing the joined error messages. - If
result.success, callsnext().
- Applying Validation Middleware:
- In
watchListRoutes.js,import { validateRequest } from '../middleware/validateRequest.js';andimport { addToListSchema } from '../validators/watchListValidators.js';. router.post('/', authMiddleware, validateRequest(addToListSchema), addToListController);applies both authentication and validation middleware to the route.
- In
Deployment with Hostinger
- Hostinger VPS Setup:
- Hostinger is recommended for VPS hosting, highlighting Black Friday deals and free domain offers.
- A VPS plan (KVM2) is chosen.
- Server Location: Choose closest to target users for best latency.
- Operating System: Ubuntu 24.04 is selected.
- Root Password: Set a strong root password.
- SSH Key: Generate an SSH key pair (
ssh-keygen -t rsa -b 4096 -C "your_email@example.com") and add the public key to Hostinger for secure, passwordless SSH access. - Malware Scanner: Enabled for VPS security.
- SSH into VPS:
ssh root@YOUR_VPS_IP_ADDRESSis used to connect to the virtual server.
- Install Essential Tools on VPS:
sudo apt update && sudo apt upgrade -y: Updates package lists and upgrades installed packages.sudo apt install -y curl git: Installscurl(for downloading) andgit(for cloning repositories).- Install Node.js:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -(or latest LTS version) downloads the Node.js setup script.sudo apt install -y nodejs: Installs Node.js and npm.node -vandnpm -vverify installation.
- Clone Project to VPS:
- GitHub Repository: The local project is initialized as a Git repository (
git init .), committed, and pushed to a new GitHub repository. (.envis excluded via.gitignore). - VPS Directory:
sudo mkdir -p /var/www: Creates the standard web server directory.cd /var/www: Navigates into the directory.sudo mkdir backend-course: Creates a project-specific folder.cd backend-course: Navigates into the project folder.git clone YOUR_REPO_URL .: Clones the GitHub repository into the current directory.
- GitHub Repository: The local project is initialized as a Git repository (
- Configure Environment Variables on VPS:
nano .env: Creates and opens the.envfile on the VPS.- The content of the local
.envfile is copied and pasted, ensuringNODE_ENVis set toproduction. - Save and exit Nano (Ctrl+X, Y, Enter).
- Adjust Server for Production:
- In
server.js, theapp.listenfunction is modified toapp.listen(PORT, '0.0.0.0', ...)to listen on all network interfaces, crucial for a VPS. - The updated code is pushed to GitHub and pulled on the VPS (
git pull).
- In
- Install Project Dependencies on VPS:
npm install: Installs all project dependencies (frompackage.json) on the VPS.
- Run API with PM2:
- PM2 Introduction: A process manager to keep Node.js apps running continuously, restart on crashes, and manage background processes.
sudo npm install -g pm2: Installs PM2 globally on the VPS.pm2 start src/server.js --name movie-watchlist-API: Starts the API using PM2, giving it a recognizable name.pm2 save: Saves the current PM2 process list.pm2 startup systemd: Configures PM2 to start automatically on VPS boot.- Testing confirms the API is accessible via
http://YOUR_VPS_IP_ADDRESS:5001.
- Domain and Nginx Setup:
- Claim Free Domain: A free
.clouddomain (e.g.,moviewatchlist.cloud) is claimed via Hostinger. - DNS A Record: An A record is added to the domain's DNS settings, pointing
api.moviewatchlist.cloudto the VPS's IP address. - Nginx Installation:
sudo apt install -y nginx. - Nginx Configuration:
sudo nano /etc/nginx/sites-available/moviewatchlist: Creates a new Nginx config file.- The config file is populated to:
listen 80;: Listen on the standard HTTP port.server_name api.moviewatchlist.cloud;: Define the domain name.location / { proxy_pass http://127.0.0.1:5001; ... }: Proxy requests from port 80 to the Node.js app running onlocalhost:5001.
- Save and exit.
- Enable Nginx Config:
sudo ln -s /etc/nginx/sites-available/moviewatchlist /etc/nginx/sites-enabled/: Creates a symbolic link to enable the config.sudo rm /etc/nginx/sites-enabled/default: Removes the default Nginx config.
- Test and Restart Nginx:
sudo nginx -t: Tests Nginx configuration for syntax errors.sudo systemctl restart nginx: Restarts Nginx to apply changes.
- Testing confirms the API is now accessible via
http://api.moviewatchlist.cloud(without specifying the port).
- Claim Free Domain: A free
- HTTPS with Certbot:
- Certbot Installation:
sudo apt install -y certbot python3-certbot-nginx. - Generate SSL Certificate:
sudo certbot --nginx -d api.moviewatchlist.cloud: Automatically obtains and installs a Let's Encrypt SSL certificate, configuring Nginx for HTTPS. - Follow prompts (email, terms of service, renewal preferences).
- Testing confirms the API is now accessible via
https://api.moviewatchlist.cloud.
- Certbot Installation:
Conclusion and Takeaways
The video provides a comprehensive, step-by-step guide to building a secure and scalable REST API with Node.js and Express. It covers essential backend development concepts from project setup and dependency management to advanced topics like database ORMs, robust authentication with JWTs and HTTP-only cookies, request validation with ZOD, and full production deployment on a VPS using Hostinger, PM2, Nginx, and Certbot for HTTPS. The emphasis on clean code, best practices, and understanding the "why" behind each step transforms backend development from "magic" to "logical." The tutorial also highlights the value of tools like Requestly for efficient API testing.
Chat with this Video
AI-PoweredHi! I can answer questions about this video "Backend Complete Course | NodeJS, ExpressJS, JWT, PostgreSQL, Prisma...". What would you like to know?