Backend Complete Course | NodeJS, ExpressJS, JWT, PostgreSQL, Prisma...

By PedroTech

Share:

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

  1. Project Initialization:
    • A new folder, backend-course, is created and opened in VS Code.
    • npm init -y is run to create a package.json file, which serves as the blueprint for the Node.js project, listing dependencies, scripts, and basic project info.
  2. Project Structure:
    • A src folder is created to contain all source material.
    • server.js is designated as the entry point for the API, responsible for creating and starting the server.
  3. 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.
  4. Server Initialization (server.js):
    • import express from 'express'; (after configuring package.json for 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 and 0.0.0.0 to accept traffic from any network interface.
  5. Running the Server with Nodemon:
    • A dev script is added to package.json: "dev": "nodemon src/server.js".
    • npm run dev starts the server, leveraging Nodemon for automatic restarts.

API Endpoints and Routing Fundamentals

  1. 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).
  2. 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.
  3. 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., baseURL for http://localhost:5001) are used for reusability.
    • A GET request to {{baseURL}}/hello is made, demonstrating a successful 200 OK response.
  4. 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 routes folder is created.
    • movieRoutes.js is 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 using app.use('/movies', movieRoutes);. This prefixes all routes in movieRoutes with /movies.
    • Testing confirms {{baseURL}}/movies/hello works, while {{baseURL}}/hello no longer does.
    • Multiple HTTP methods (GET, POST, PUT, DELETE) can share the same path if their HTTP method differs.

Database Integration with Prisma and Neon

  1. 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.
  2. 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.
  3. Prisma Setup:
    • npx prisma init initializes Prisma in the project, creating a prisma folder and schema.prisma file.
    • npm install prisma --save-dev and npm install @prisma/client install the necessary Prisma packages.
    • The Prisma VS Code extension is recommended for syntax highlighting.
  4. Environment Variables (.env):
    • A .env file is created to store sensitive information like the DATABASE_URL.
    • The copied Neon connection string is pasted into .env as DATABASE_URL.
    • npm install dotenv is installed to load environment variables.
    • import 'dotenv/config'; is added at the top of server.js to load .env variables.
  5. Database Configuration (config/db.js):
    • A config folder is created with db.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 the NODE_ENV (development or production).
    • connectDB and disconnectDB async functions are created using prisma.$connect() and prisma.$disconnect(), with try-catch blocks for error handling and process.exit(1) on connection failure.
    • These functions and the prisma instance are exported.
  6. Integrating DB Connection in server.js:
    • connectDB and disconnectDB are imported and called in server.js.
    • Robust error handling for unhandledRejection, uncaughtException, and SIGTERM events is added to gracefully disconnect the database and shut down the server, preventing memory leaks.
  7. Defining Database Schema (prisma/schema.prisma):
    • Models are defined for User, Movie, and WatchListItem.
    • User Model: id (UUID, default), name (string), email (string, unique), password (string), createdAt (DateTime, default now()).
    • Movie Model: id (UUID), title (string), overview (string, optional), year (int), genres (string array, default empty), runtime (int, optional), poster (string, optional), createdBy (string, references User.id), createdAt (DateTime).
    • WatchListItem Model: id (UUID), userId (string, references User.id), movieId (string, references Movie.id), status (custom WatchListStatus enum, default planned), rating (int, optional), notes (string, optional), createdAt (DateTime), updatedAt (DateTime, default now(), onUpdate now()).
    • Enum WatchListStatus: planned, watching, completed, dropped.
    • Relationships: @@unique([userId, movieId]) is added to WatchListItem to ensure a user can only add a specific movie once. @@relation decorators are used to define foreign key relationships and onDelete: Cascade for referential integrity.
    • The generator client in schema.prisma is modified to remove output and change provider to js for better type generation.
  8. 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 generate generates Prisma Client types for autocompletion.
    • Verification on Neon dashboard confirms table creation.

Authentication and Authorization

  1. Controller Pattern:
    • A controllers folder is created.
    • authController.js will contain the logic for authentication routes.
    • Routes (authRoutes.js) will import and call controller functions, keeping route definitions clean.
  2. Register User (POST /auth/register):
    • Body Parsing Middleware: app.use(express.json()); and app.use(express.urlencoded({ extended: true })); are added to server.js to parse JSON and URL-encoded data from request bodies.
    • Controller Logic (authController.js):
      • Destructures name, email, password from req.body.
      • Check for existing user: prisma.user.findUnique({ where: { email } }). If userExists, returns 400 Bad Request with "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 Created with success status and user details (ID, name, email).
  3. Login User (POST /auth/login):
    • Controller Logic (authController.js):
      • Destructures email, password from req.body.
      • Find user: prisma.user.findUnique({ where: { email } }). If no user, returns 401 Unauthorized with "Invalid email or password."
      • Verify password: const isPasswordValid = await bcrypt.compare(password, user.password);. If not valid, returns 401 Unauthorized with "Invalid email or password."
      • Returns 200 OK with success status and user details.
  4. 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_SECRET and JWT_EXPIRES_IN environment variables are added to .env (secret generated using openssl rand -base64 32, expiry e.g., "7d").
      • generateToken(userId, res) function:
        • Creates a payload object { 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.
    • The generateToken function is called in both register and login controllers, passing the userId and res object.
  5. 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 OK with "Logged out successfully."
  6. Authentication Middleware (middleware/authMiddleware.js):
    • A middleware folder is created with authMiddleware.js.
    • protect (or authMiddleware) async function: (req, res, next)
      • Extract Token:
        • Checks req.headers.authorization for a "Bearer" token. If found, splits and extracts the token.
        • Else, checks req.cookies.jwt.
      • 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").
    • 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 userId in addToListController is now accessed via req.user.id instead of req.body.userId, ensuring the request is made by the authenticated user.
  7. Seed File for Mock Data (prisma/seed.js):
    • A seed.js file is created to populate the database with mock movie data.
    • It uses PrismaClient to create a new user (Tarantino) and then 10 movie entries, associating them with Tarantino's ID.
    • A seed-movies script is added to package.json: "seed-movies": "node prisma/seed.js".
    • npm run seed-movies executes the script.

Request Validation with ZOD

  1. Validation Introduction: The importance of validating incoming request bodies to ensure data integrity and prevent errors is emphasized.
  2. ZOD Installation: npm install zod.
  3. Validators Folder: A validators folder is created, with watchListValidators.js for watch list related schemas.
  4. Defining Schemas (watchListValidators.js):
    • import { z } from 'zod';
    • export const addToListSchema = z.object({ ... }); defines a schema for the add to watch list request 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.
  5. 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. safeParse returns an object with success (boolean) and data or error.
    • If !result.success, it formats the ZOD errors using result.error.format()._errors.flat() to get a clean array of error messages.
    • Returns 400 Bad Request with a JSON containing the joined error messages.
    • If result.success, calls next().
  6. Applying Validation Middleware:
    • In watchListRoutes.js, import { validateRequest } from '../middleware/validateRequest.js'; and import { addToListSchema } from '../validators/watchListValidators.js';.
    • router.post('/', authMiddleware, validateRequest(addToListSchema), addToListController); applies both authentication and validation middleware to the route.

Deployment with Hostinger

  1. 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.
  2. SSH into VPS:
    • ssh root@YOUR_VPS_IP_ADDRESS is used to connect to the virtual server.
  3. 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: Installs curl (for downloading) and git (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 -v and npm -v verify installation.
  4. 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. (.env is 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.
  5. Configure Environment Variables on VPS:
    • nano .env: Creates and opens the .env file on the VPS.
    • The content of the local .env file is copied and pasted, ensuring NODE_ENV is set to production.
    • Save and exit Nano (Ctrl+X, Y, Enter).
  6. Adjust Server for Production:
    • In server.js, the app.listen function is modified to app.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).
  7. Install Project Dependencies on VPS:
    • npm install: Installs all project dependencies (from package.json) on the VPS.
  8. 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.
  9. Domain and Nginx Setup:
    • Claim Free Domain: A free .cloud domain (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.cloud to 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 on localhost: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).
  10. 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.

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-Powered

Hi! I can answer questions about this video "Backend Complete Course | NodeJS, ExpressJS, JWT, PostgreSQL, Prisma...". What would you like to know?

Chat is based on the transcript of this video and may not be 100% accurate.

Related Videos

Ready to summarize another video?

Summarize YouTube Video