Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Designing For TV: Principles, Patterns And Practical Guidance (Part 2)

      September 5, 2025

      Neo4j introduces new graph architecture that allows operational and analytics workloads to be run together

      September 5, 2025

      Beyond the benchmarks: Understanding the coding personalities of different LLMs

      September 5, 2025

      Top 10 Use Cases of Vibe Coding in Large-Scale Node.js Applications

      September 3, 2025

      Building smarter interactions with MCP elicitation: From clunky tool calls to seamless user experiences

      September 4, 2025

      From Zero to MCP: Simplifying AI Integrations with xmcp

      September 4, 2025

      Distribution Release: Linux Mint 22.2

      September 4, 2025

      Coded Smorgasbord: Basically, a Smorgasbord

      September 4, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Drupal 11’s AI Features: What They Actually Mean for Your Team

      September 5, 2025
      Recent

      Drupal 11’s AI Features: What They Actually Mean for Your Team

      September 5, 2025

      Why Data Governance Matters More Than Ever in 2025?

      September 5, 2025

      Perficient Included in the IDC Market Glance for Digital Business Professional Services, 3Q25

      September 5, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      How DevOps Teams Are Redefining Reliability with NixOS and OSTree-Powered Linux

      September 5, 2025
      Recent

      How DevOps Teams Are Redefining Reliability with NixOS and OSTree-Powered Linux

      September 5, 2025

      Distribution Release: Linux Mint 22.2

      September 4, 2025

      ‘Cronos: The New Dawn’ was by far my favorite experience at Gamescom 2025 — Bloober might have cooked an Xbox / PC horror masterpiece

      September 4, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Build a Telehealth App Using Stream Video and Chat SDK in React

    How to Build a Telehealth App Using Stream Video and Chat SDK in React

    July 19, 2025

    Remember when the COVID-19 pandemic moved everything online – doctor’s visits included – and staying home became the safest option?

    That moment kicked off a massive shift in how healthcare gets delivered.

    Telehealth became more than a workaround. It’s now a core part of modern care. As demand grows, developers are stepping up to build secure, real-time platforms that connect patients and providers from anywhere.

    In this article, you’ll learn how to build a telehealth application with Stream’s React Video and Chat SDKs. You’ll set up authentication, create video calls, enable messaging, and design a functional user interface that mimics real-world telehealth workflows.

    Let’s dive in.

    Outline

    • Introduction

    • Prerequisites

    • App Structure

    • Project Setup

    • Backend Setup

    • Frontend Setup

    • Stream Chat and Video Integration

    • Chat and Video Function (Frontend)

    • Project Demo

    • Conclusion

    Prerequisites

    Before you start this tutorial, make sure you have:

    • A basic understanding of React.

    • Node.js and npm/yarn installed on your computer

    • A free account with Stream

    • Familiarity with Stream SDKs

    • A basic understanding of Tailwind CSS for styling

    • Experience with VS Code and Postman (for testing APIs)

    App Structure

    Before diving into the code, it’s helpful to understand how the app is structured.

    <span class="hljs-section"># App Flow Structure</span>
    
    <span class="hljs-bullet">-</span> Landing Page  
    <span class="hljs-bullet">  -</span> Navigation  
    <span class="hljs-bullet">    -</span> Home  
    <span class="hljs-bullet">    -</span> About  
    <span class="hljs-bullet">    -</span> Sign Up  
    <span class="hljs-bullet">      -</span> Verify Account  
    <span class="hljs-bullet">        -</span> Log In  
    <span class="hljs-bullet">          -</span> Dashboard  
    <span class="hljs-bullet">            -</span> Stream Chat  
    <span class="hljs-bullet">            -</span> Stream Video  
    <span class="hljs-bullet">            -</span> Log Out
    

    Project Setup

    Before getting started, create two folders: “Frontend” to handle the client-side code and “Backend” for the server-side logic. This separation allows you to manage both parts of your application efficiently.

    Set Up React for the Frontend

    Once the folders are created, you can set up the React application in the Frontend folder.

    First, navigate to the Frontend directory using the command cd Frontend.

    Now you can initialize your React project. You’ll use Vite, a fast build tool for React applications.

    To create your React project, run the following command:

    npm create vite@latest [project name] -- --template react

    Next, navigate to your new project folder, using the command:

    cd [project name]

    Once there, install the required dependencies by running:

    npm install

    This command installs both the node_modules folder (which contains all your project’s packages) and the package-lock.json file (which records exact versions of installed packages).

    Next, you’ll need to install Tailwind CSS for styling. Follow the Tailwind Docs for step-by-step instructions.

    Then, it’s time to set up the website. Using React, you’ll create the home sign-in/log-in pages. Both will be nested together using React-router-dom.

    Here’s what the home page looks like:

    Telehealth Home Page

    Now, the user has a place to land whenever they visit the website.

    Let’s set up the backend.

    Backend Setup

    Installing Required Packages

    Before setting up your project’s backend, it’s important to define what your project needs to offer. This will help you install all the necessary packages in one go.

    Start by moving into the backend folder using the command: cd Backend

    Inside the Backend directory, initialize your Node.js project using npm install

    This will create a package.json file, which stores metadata and dependencies for your project.

    Next, install all the dependencies needed to build your backend. Run the following command:

    npm i bcryptjs cookie-parser cors dotenv express jsonwebtoken mongoose nodemailer validator nodemon

    Here’s a brief overview of what each package does:

    • bcryptjs: Encrypts user passwords for secure storage.

    • Cookie-parser: Handles cookies in your application.

    • CORS: Middleware that enables cross-origin requests – essential for frontend-backend communication.

    • dotenv: Loads environment variables from a .env file into process.env.

    • Express: The core framework for building your server and API routes.

    • jsonwebtoken: Generates and verifies JWT tokens for authentication.

    • Mongoose: Connects your app to a MongoDB database.

    • nodemailer: Handles sending emails from your application.

    • Validator: Validates user inputs like email, strings, and so on.

    • nodemon: Automatically restarts your server when changes are made to files.

    Once your packages are installed, create two key files in the backend directory: App.js, which contains your app logic, middleware, and route handlers, and server.js, responsible for initializing and configuring your server.

    Next, you have to update your package.json start script. Head to the package.json file in your backend directory and replace the default script:

    <span class="hljs-string">"scripts"</span>: {
        <span class="hljs-attr">"test"</span>: <span class="hljs-string">"echo”Error: no test specified" && exit 1”
      }</span>
    

    with this:

    <span class="hljs-string">"scripts"</span>: {
        <span class="hljs-attr">"start"</span>: <span class="hljs-string">"nodemon server.js"</span>
      }
    

    This setup allows you to run your server using nodemon, automatically reloading it when changes are made. This helps boost productivity during development.

    To check if your backend setup is correct, open the server.js file and add a test log: console.log (“Any of your Log Message”). Then, head to your terminal in the backend directory, and run npm start. You should see the log message in the terminal, confirming that your backend is running.

    Backend Server Testing

    App.js Setup

    In the App.js file, start by importing the packages you initially installed.

    <span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
    
    <span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);
    
    <span class="hljs-keyword">const</span> cookieParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cookie-parser"</span>);
    
    <span class="hljs-keyword">const</span> app = express();
    
    
    app.use(
    
      cors({
    
    <span class="hljs-attr">origin</span>: [
    
       <span class="hljs-string">"http://localhost:5173"</span>,
    
    ],
    
    <span class="hljs-attr">credentials</span>: <span class="hljs-literal">true</span>,
    
      })
    
    );
    
    app.use(express.json({ <span class="hljs-attr">limit</span>: <span class="hljs-string">"10kb"</span> }));
    
    app.use(cookieParser());
    
    <span class="hljs-built_in">module</span>.exports = app;
    

    Here’s what the code above does:

    The require statements import express, cors, and cookie-parser, which are essential for creating your backend server, handling cross-origin requests, and parsing cookies.

    The const app = express(); command sets up a new instance of an Express application.

    app.use(cors({ origin: ["http://localhost:5173"], credentials: true })); grants permission or allows requests from your frontend and enables cookie sharing between the frontend and backend of your application. This is important for authentication.

    The app.use(express.json({ limit: "10kb" })); command is a middleware function that ensures the server can process incoming JSON payloads and protects against overly large requests, which could be used in DoS attacks.

    The app.use(cookieParser()); command makes cookies available via req.cookies.

    Last, the module.exports = app; command allows the app to be imported in other files, especially in server.js, which is where the app will be started.

    Server.js Setup

    Once App.js is set up, the next step is to create and configure your server in a new file called server.js.

    Before doing that, ensure you have a MongoDB database set up. If you don’t have one yet, you can follow this video tutorial to set up a MongoDB database.

    After setting up MongoDB, you will receive a username and password. Copy the password, head to your backend directory, and create a .env file to store it.

    After you have stored the password, head back to complete your database setup.

    Next, click on the “Create Database User” button, then click on the choose connection method option. Since we are using Node.js for this project, choose the “Drivers” option. This gives you the database connection string (you should see it at No. 3).

    Database-String-Auth

    Then head to your .env and paste it there, and add auth immediately after you have “.net/”.

    Here’s what it looks like:

    mongodb+srv://<username>:<password>@cluster0.qrrtmhs.mongodb.net/auth?retryWrites=true&w=majority&appName=Cluster0

    Backend config.env file

    Lastly, whitelist your IP address. This ensures your backend can connect to MongoDB from your local machine or any environment during development.

    To allow your application to connect to the database:

    • Go to the “Network Access” section in the Security sidebar of your MongoDB dashboard.

    • Click on “ADD IP ADDRESS.”

    • Choose “Allow Access from Anywhere”, then click on Confirm.

    At this point, you can set up your server.js

    <span class="hljs-comment">//server.js</span>
    <span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();
    <span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
    <span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>); <span class="hljs-comment">//to Manage our environment variable</span>
    
    dotenv.config({ <span class="hljs-attr">path</span>: <span class="hljs-string">"./config.env"</span> });
    <span class="hljs-comment">// console.log(process.env.NODE_ENV);</span>
    
    <span class="hljs-keyword">const</span> app = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./app"</span>);
    
    <span class="hljs-keyword">const</span> db = process.env.DB;
    <span class="hljs-comment">//connect the application to database using MongoDB</span>
    
    mongoose
      .connect(db)
      .then(<span class="hljs-function">() =></span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"DB connection Successful"</span>);
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =></span> {
        <span class="hljs-built_in">console</span>.log(err);
      });
    
    <span class="hljs-keyword">const</span> port = process.env.PORT || <span class="hljs-number">3000</span>;
    <span class="hljs-comment">// console.log(process.env.PORT)</span>
    
    app.listen(port, <span class="hljs-function">() =></span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`App running on port <span class="hljs-subst">${port}</span>`</span>);
    });
    

    The server.js file is responsible for handling all server-related functions and logic. From the code above, the server.js file loads the environment variables using dotenv, connects your backend to MongoDB using mongoose, and starts the Express server. It gets the database URL and port from the config.env file, connects to the database, then runs your application on the specified port (8000).

    If the specified port is not found, it falls back to port 3000 and a confirmation message is printed to the console indicating that the server is up and running on the specified port.

    server-js Telehealth App

    Connect the Database to MongoDB Compass

    First, download the MongoDB Compass app. (Go here to download and install: https://www.mongodb.com/try/download/compass). The MongoDB Compass app makes it easy for us to manage our data.

    Once the installation is complete, open the app and click on Click to add new connection. Go to your .env file, copy the connection string you initially got when setting up MongoDB, paste it in the URL section, and then click on “connect.” This setup helps you manage your data when you create and delete users.

    Mongo-DB-Compass

    Set up an Advanced Error Handling Method

    You’ll now create an advanced error-handling mechanism. To do so, create a utils folder in your backend, a catchAsync.js file in the utils folder, and add this code:

    <span class="hljs-comment">//catchAsync.js</span>
    <span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">fn</span>) =></span> {
      <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =></span> {
        fn(req, res, next).catch(next);
      };
    };
    

    Next, create an appError.js file still in your utils folder. In the appError.js file, add the following command:

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppError</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Error</span> </span>{
      <span class="hljs-keyword">constructor</span>(message, statusCode) {
        <span class="hljs-built_in">super</span>(message);
    
        <span class="hljs-built_in">this</span>.statusCode = statusCode;
        <span class="hljs-built_in">this</span>.status = <span class="hljs-string">`<span class="hljs-subst">${statusCode}</span>`</span>.startsWith(<span class="hljs-string">"4"</span>) ? <span class="hljs-string">"fail"</span> : <span class="hljs-string">"error"</span>;
        <span class="hljs-built_in">this</span>.isOperational = <span class="hljs-literal">true</span>;
    
        <span class="hljs-built_in">Error</span>.captureStackTrace(<span class="hljs-built_in">this</span>, <span class="hljs-built_in">this</span>.constructor);
      }
    }
    
    <span class="hljs-built_in">module</span>.exports = AppError;
    

    The code above is helpful in tracking and tracing errors. It also provides you with the URL and file location where your error might be occurring, which helps with cleaner error handling and debugging.

    Next, let’s create a global error handler. Start by creating a new folder in the backend directory, and name it “controller”. In the controller folder, create your global error handling file. You can name it anything you like. In this example, it’s called globalErrorHandler.js.

    Your globalErrorHandler file will define several functions that handle specific error types, such as database issues, validation failures, or even JWT problems and return a nicely formatted error response for users. For the globalErrorHandler to work properly, you have to create a controller function.

    So, next, create an errorController.js file (still inside the controller folder). The errorController.js file responds to the user whenever an error is caught, sending the error in JSON format.

    globalErrorHandler.js:

    <span class="hljs-comment">// globalErrorHandler.js</span>
    <span class="hljs-keyword">const</span> AppError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/appError"</span>);
    
    <span class="hljs-keyword">const</span> handleCastErrorDB = <span class="hljs-function">(<span class="hljs-params">err</span>) =></span> {
      <span class="hljs-keyword">const</span> message = <span class="hljs-string">`Invalid <span class="hljs-subst">${err.path}</span>: <span class="hljs-subst">${err.value}</span>.`</span>;
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AppError(message, <span class="hljs-number">400</span>);
    };
    
    <span class="hljs-keyword">const</span> handleDuplicateFieldsDB = <span class="hljs-function">(<span class="hljs-params">err</span>) =></span> {
      <span class="hljs-keyword">const</span> value = err.keyValue ? <span class="hljs-built_in">JSON</span>.stringify(err.keyValue) : <span class="hljs-string">"duplicate field"</span>;
      <span class="hljs-keyword">const</span> message = <span class="hljs-string">`Duplicate field value: <span class="hljs-subst">${value}</span>. Please use another value!`</span>;
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AppError(message, <span class="hljs-number">400</span>);
    };
    
    <span class="hljs-keyword">const</span> handleValidationErrorDB = <span class="hljs-function">(<span class="hljs-params">err</span>) =></span> {
      <span class="hljs-keyword">const</span> errors = <span class="hljs-built_in">Object</span>.values(err.errors).map(<span class="hljs-function">(<span class="hljs-params">el</span>) =></span> el.message);
      <span class="hljs-keyword">const</span> message = <span class="hljs-string">`Invalid input: <span class="hljs-subst">${errors.join(<span class="hljs-string">". "</span>)}</span>`</span>;
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AppError(message, <span class="hljs-number">400</span>);
    };
    
    <span class="hljs-keyword">const</span> handleJWTError = <span class="hljs-function">() =></span>
      <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Invalid token. Please log in again!"</span>, <span class="hljs-number">401</span>);
    <span class="hljs-keyword">const</span> handleJWTExpiredError = <span class="hljs-function">() =></span>
      <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Your token has expired! Please log in again."</span>, <span class="hljs-number">401</span>);
    
    <span class="hljs-built_in">module</span>.exports = {
      handleCastErrorDB,
      handleDuplicateFieldsDB,
      handleValidationErrorDB,
      handleJWTError,
      handleJWTExpiredError,
    };
    

    Here’s what the code above does:

    The const handleCastErrorDB = (err) =>.. section handles MongoDB CastError which usually happens when an invalid ID is passed, and returns a 400 Bad Request error response using your AppError class.

    The const handleDuplicateFieldsDB = (err) =>... checks and handles MongoDB duplicate key errors, such as trying to register an email or username that’s already taken and returns a 400 Bad Request error.

    The const handleValidationErrorDB = (err) =>... handles Mongoose validation errors (like required fields missing or wrong data types). It gathers all the individual validation error messages and combines them into one.

    The const handleJWTError = () => and const handleJWTExpiredError = () => handle errors which might occur as a result of invalid, tampered, or expired JWT tokens and return a 401 Unauthorized error response.

    The module.exports = {……}; section exports all the individual error handlers so they can be used in the errorController.js file.

    errorController.js:

    <span class="hljs-comment">//errorController.js</span>
    <span class="hljs-keyword">const</span> errorHandlers = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./globalErrorHandler"</span>);
    
    <span class="hljs-keyword">const</span> {
      handleCastErrorDB,
      handleDuplicateFieldsDB,
      handleValidationErrorDB,
      handleJWTError,
      handleJWTExpiredError,
    } = errorHandlers;
    
    <span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =></span> {
      err.statusCode = err.statusCode || <span class="hljs-number">500</span>;
      err.status = err.status || <span class="hljs-string">"error"</span>;
    
      <span class="hljs-keyword">let</span> error = { ...err, <span class="hljs-attr">message</span>: err.message };
    
      <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">"CastError"</span>) error = handleCastErrorDB(err);
      <span class="hljs-keyword">if</span> (err.code === <span class="hljs-number">11000</span>) error = handleDuplicateFieldsDB(err);
      <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">"ValidationError"</span>) error = handleValidationErrorDB(err);
      <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">"JsonWebTokenError"</span>) error = handleJWTError();
      <span class="hljs-keyword">if</span> (err.name === <span class="hljs-string">"TokenExpiredError"</span>) error = handleJWTExpiredError();
    
      res.status(error.statusCode).json({
        <span class="hljs-attr">status</span>: error.status,
        <span class="hljs-attr">message</span>: error.message,
        ...(process.env.NODE_ENV === <span class="hljs-string">"production"</span> && { error, <span class="hljs-attr">stack</span>: err.stack }),
      });
    };
    

    To ensure your error-handling function works properly, head to your App.js and add the command:

    <span class="hljs-comment">//import command</span>
    <span class="hljs-keyword">const</span> globalErrorHandler = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./controller/errorController"</span>);
    <span class="hljs-keyword">const</span> AppError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./utils/appError"</span>);
    
    <span class="hljs-comment">//Catch unknown routes</span>
    app.all(<span class="hljs-string">"/{*any}"</span>, <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =></span> {
      next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">`Can't find <span class="hljs-subst">${req.originalUrl}</span> on this server!`</span>, <span class="hljs-number">404</span>)); });
    
    app.use(globalErrorHandler);
    

    This ensures that all errors are properly handled and sends the error response to the user.

    Create User Model

    To build a user model, create a new folder in the backend directory and name it “model.” Inside the model folder, create a new file named userModel.js.

    The userModel.js file is built essentially for user authentication and security. It provides a validation-rich schema for managing users using Mongoose, which maps how user data is structured in the MongoDB database. It includes validations, password hashing, and methods to compare user passwords securely.

    <span class="hljs-comment">//userModel.js</span>
    <span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);
    <span class="hljs-keyword">const</span> validator = <span class="hljs-built_in">require</span>(<span class="hljs-string">"validator"</span>);
    <span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"bcryptjs"</span>);
    
    <span class="hljs-keyword">const</span> userSchema = <span class="hljs-keyword">new</span> mongoose.Schema(
      {
        <span class="hljs-attr">username</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"Please provide username"</span>], <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">minlength</span>: <span class="hljs-number">3</span>, <span class="hljs-attr">maxlength</span>: <span class="hljs-number">30</span>, <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span>,},
        <span class="hljs-attr">email</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"Please Provide an email"</span>], <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">lowercase</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">validate</span>: [validator.isEmail, <span class="hljs-string">"Please provide a valid email"</span>],},
        <span class="hljs-attr">password</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"Please provide a Password"</span>], <span class="hljs-attr">minlength</span>: <span class="hljs-number">8</span>, <span class="hljs-attr">select</span>: <span class="hljs-literal">false</span>,},
        <span class="hljs-attr">passwordConfirm</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>, <span class="hljs-attr">required</span>: [<span class="hljs-literal">true</span>, <span class="hljs-string">"Please confirm your Password"</span>],
         <span class="hljs-attr">validate</span>: {<span class="hljs-attr">validator</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">el</span>) </span>{<span class="hljs-keyword">return</span> el === <span class="hljs-built_in">this</span>.password;},
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Passwords do not match"</span>,
          },
        },
        <span class="hljs-attr">isVerified</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">Boolean</span>, <span class="hljs-attr">default</span>: <span class="hljs-literal">false</span>,}, <span class="hljs-attr">otp</span>: <span class="hljs-built_in">String</span>,
        <span class="hljs-attr">otpExpires</span>: <span class="hljs-built_in">Date</span>,
         <span class="hljs-attr">resetPasswordOTP</span>: <span class="hljs-built_in">String</span>,
          <span class="hljs-attr">resetPasswordOTPExpires</span>: <span class="hljs-built_in">Date</span>,
        <span class="hljs-attr">createdAt</span>: {<span class="hljs-attr">type</span>: <span class="hljs-built_in">Date</span>, <span class="hljs-attr">default</span>: <span class="hljs-built_in">Date</span>.now,},}, { <span class="hljs-attr">timestamps</span>: <span class="hljs-literal">true</span> });
    
    <span class="hljs-comment">// Hash password before saving</span>
    userSchema.pre(<span class="hljs-string">"save"</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">next</span>) </span>{
      <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isModified(<span class="hljs-string">"password"</span>)) <span class="hljs-keyword">return</span> next();
    
      <span class="hljs-built_in">this</span>.password = <span class="hljs-keyword">await</span> bcrypt.hash(<span class="hljs-built_in">this</span>.password, <span class="hljs-number">12</span>);
      <span class="hljs-built_in">this</span>.passwordConfirm = <span class="hljs-literal">undefined</span>; <span class="hljs-comment">// Remove passwordConfirm before saving</span>
      next();
    });
    
    <span class="hljs-keyword">const</span> User = mongoose.model(<span class="hljs-string">"User"</span>, userSchema);
    <span class="hljs-built_in">module</span>.exports = User;
    

    Auth Controller

    Now, you can create logic that regulates your user’s authentication process. This authentication logic consists of the sign-up, sign-in (log-in), OTP, and so on.

    To do so, first head to your controller folder and create a new file. Call it authController.js because it handles the authentication flow of your project.

    After you’ve created the file, you’ll create your sign-up function.

    <span class="hljs-comment">//import</span>
    <span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../model/userModel"</span>);
    <span class="hljs-keyword">const</span> AppError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/appError"</span>);
    <span class="hljs-keyword">const</span> catchAsync = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/catchAsync"</span>);
    <span class="hljs-keyword">const</span> generateOtp = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/generateOtp"</span>);
    <span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"jsonwebtoken"</span>);
    <span class="hljs-keyword">const</span> sendEmail = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/email"</span>)
    
    <span class="hljs-built_in">exports</span>.signup = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) => {
      <span class="hljs-keyword">const</span> { email, password, passwordConfirm, username } = req.body;
    
      <span class="hljs-keyword">const</span> existingUser = <span class="hljs-keyword">await</span> User.findOne({ email });
    
      <span class="hljs-keyword">if</span> (existingUser) <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Email already registered"</span>, <span class="hljs-number">400</span>));
    
      <span class="hljs-keyword">const</span> otp = generateOtp();
    
      <span class="hljs-keyword">const</span> otpExpires = <span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">24</span> <span class="hljs-number">60</span> <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>; <span class="hljs-comment">//when thhe otp will expire (1 day)</span>
    
      <span class="hljs-keyword">const</span> newUser = <span class="hljs-keyword">await</span> User.create({
        username,
        email,
        password,
        passwordConfirm,
        otp,
        otpExpires,
      });
    
      <span class="hljs-comment">//configure email sending functionality</span>
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> sendEmail({
          <span class="hljs-attr">email</span>: newUser.email,
          <span class="hljs-attr">subject</span>: <span class="hljs-string">"OTP for email Verification"</span>,
          <span class="hljs-attr">html</span>: <span class="hljs-string">`<h1>Your OTP is : <span class="hljs-subst">${otp}</span></h1>`</span>,
        });
    
        createSendToken(newUser, <span class="hljs-number">200</span>, res, <span class="hljs-string">"Registration successful"</span>);
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Email send error:"</span>, error);
        <span class="hljs-keyword">await</span> User.findByIdAndDelete(newUser.id);
        <span class="hljs-keyword">return</span> next(
          <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"There is an error sending the email. Try again"</span>, <span class="hljs-number">500</span>)
        );
      }
    });
    

    const { email, password, passwordConfirm, username } = req.body; extracts the necessary registration details from the incoming request: email, password, password confirmation, and username during user sign-up.

    const existingUser = await User.findOne({ email }); checks the database to see if a user already exists with the given email. If yes, it sends an error response using your AppError utility.

    const otp = generateOtp(); generates the OTP, and const otpExpires = Date.now()….. is used to set the OTP to expire at a specified time or day.

    With const newUser = await User.create({…});, the new user is saved in MongoDB with their credentials and the OTP info, with the password automatically hashed.

    await sendEmail({…}); sends an email to the user. This email contains their sign-in OTP. If the email is sent successfully, createSendToken(newUser, 200, res, "Registration successful"); (which is a utility function) generates a JWT token and sends it in the response with a success message.

    If the email fails to send or something goes wrong, await User.findByIdAndDelete(newUser.id); deletes the user from the database to keep things clean, and an error message of There is an error sending the email. Try again", 500 is returned.

    Generate OTP

    To ensure that your users’ OTP is successfully sent to them, in the utils folder, create a new file and name it generateOtp.js. Then add the code:

    <span class="hljs-built_in">module</span>.exports = <span class="hljs-function">() =></span> {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.floor(<span class="hljs-number">1000</span> + <span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">9000</span>).toString();
    };
    

    The code above is a function that generates a user’s random 4-digit OTP and returns it as a string.

    After completing the code above, go to your authController.js and ensure you import the generateOtp.js in the import section.

    Create User Token

    Next, the user sign-in token will be created, and it will be assigned to the user upon sign-in.

    <span class="hljs-keyword">const</span> signToken = <span class="hljs-function">(<span class="hljs-params">userId</span>) =></span> {
      <span class="hljs-keyword">return</span> jwt.sign({ <span class="hljs-attr">id</span>: userId }, process.env.JWT_SECRET, {
        <span class="hljs-attr">expiresIn</span>: process.env.JWT_EXPIRES_IN || <span class="hljs-string">"90d"</span>,
      });
    };
    
    <span class="hljs-comment">//function to create the token</span>
    <span class="hljs-keyword">const</span> createSendToken = <span class="hljs-function">(<span class="hljs-params">user, statusCode, res, message</span>) =></span> {
      <span class="hljs-keyword">const</span> token = signToken(user._id);
    
      <span class="hljs-comment">//function to generate the cookie</span>
      <span class="hljs-keyword">const</span> cookieOptions = {
        <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(
          <span class="hljs-built_in">Date</span>.now() + process.env.JWT_COOKIE_EXPIRES_IN <span class="hljs-number">24</span> <span class="hljs-number">60</span> <span class="hljs-number">60</span> <span class="hljs-number">1000</span>
        ),
    
        <span class="hljs-attr">httponly</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">"production"</span>, <span class="hljs-comment">//only secure in production</span>
        <span class="hljs-attr">sameSite</span>: process.env.NODE_ENV === <span class="hljs-string">"production"</span> ? <span class="hljs-string">"none"</span> : <span class="hljs-string">"Lax"</span>,
      };
    
      res.cookie(<span class="hljs-string">"token"</span>, token, cookieOptions);
    
      user.password = <span class="hljs-literal">undefined</span>;
      user.passwordConfirm = <span class="hljs-literal">undefined</span>;
      user.otp = <span class="hljs-literal">undefined</span>;
    

    Before the code above can work perfectly, create a JWT in your .env file.

    <span class="hljs-comment">//.env</span>
    JWT_SECRET = kaklsdolrnnhjfsnlsoijfbwhjsioennbandksd;
    JWT_EXPIRES_IN = <span class="hljs-number">90</span>d
    JWT_COOKIE_EXPIRES_IN = <span class="hljs-number">90</span>
    

    The code above is how the .env file should look. Your JWT_SECRET can be anything, just as you can see in the code.

    Note: The user token creation logic should run before the sign-in logic. So in that case, the signToken and createSendToken logic should be placed at the top before the signup logic.

    Send Email

    Next, you need to configure your email sending functionality so you can automatically send the user’s OTP to their email whenever they sign in. To configure the email, head to the utils folder, create a new file, and give it a name. In this example, the name is email.js.

    In email.js, we will send emails using the nodemailer package and Gmail as a provider.

    <span class="hljs-comment">//email.js</span>
    <span class="hljs-keyword">const</span> nodemailer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'nodemailer'</span>);
    
    <span class="hljs-keyword">const</span> sendEmail = <span class="hljs-keyword">async</span> (options) => {
      <span class="hljs-keyword">const</span> transporter = nodemailer.createTransport({
        <span class="hljs-attr">service</span>: <span class="hljs-string">'Gmail'</span>,
        <span class="hljs-attr">auth</span>: {
          <span class="hljs-attr">user</span>: process.env.HOST_EMAIL,
          <span class="hljs-attr">pass</span>: process.env.EMAIL_PASS
        }
      })
    
      <span class="hljs-comment">//defining email option and structure</span>
    
      <span class="hljs-keyword">const</span> mailOptions = {
        <span class="hljs-attr">from</span>: <span class="hljs-string">`"{HOST Name}" <{HOST Email} >`</span>,
        <span class="hljs-attr">to</span>: options.email,
        <span class="hljs-attr">subject</span>: options.subject,
        <span class="hljs-attr">html</span>: options.html,
      };
      <span class="hljs-keyword">await</span> transporter.sendMail(mailOptions);
    };
    
    <span class="hljs-built_in">module</span>.exports = sendEmail;
    

    From the code above, the const nodemailer = require('nodemailer'); command imports the nodemailer package. This is a popular Node.js library for sending emails.

    The const transporter = nodemailer.createTransport({…..}) is an email transporter. Since we will be using the Gmail service provider, service will be assigned to Gmail and auth pulls your Gmail address and password from the .env file where it’s stored.

    Note: The password is not your actual Gmail password but rather your Gmail app password. You can see how you can get your Gmail password here.

    Once you’ve successfully gotten your Gmail app password, store it in your .env file.

    Route Creation

    At this point, you have finished setting up your project’s signup function. Next, you need to test whether your signup works properly using Postman. But before that, let’s set up and define a route where the signup function will be executed.

    To set up your route, create a new folder in your backend directory named “routes” and a file named userRouter.js.

    <span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
    <span class="hljs-keyword">const</span> {signup} = <span class="hljs-built_in">require</span>(“../controller/authController”);
    
    <span class="hljs-keyword">const</span> router = express.Router();
    router.post(<span class="hljs-string">"/signup"</span>, signup);
    
    <span class="hljs-built_in">module</span>.exports = router;
    

    Next, go to your App.js file and add the router to it.

    <span class="hljs-keyword">const</span> userRouter = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./routes/userRouters"</span>); <span class="hljs-comment">//Route import statement</span>
    app.use(<span class="hljs-string">"/api/v1/users"</span>, userRouter) <span class="hljs-comment">//common route for all auth, i.e sign up, log in, forget password, etc.</span>
    

    After setting up your routes, you can test your signup to see if it works. This is a post request, and the route URL will be http://localhost:8000/api/v1/users/signup.

    New user Sign up Testing

    The image above shows that the signup function works perfectly with a statusCode of 200 and an OTP code being sent to the user’s email.

    Congratulations on reaching this point! You can check your MongoDB database to see if the user is displayed there.

    Mongo-DB-Compass-User-Display

    From the image above, you can see that the user details are obtained and the password is in an encrypted form, which ensures the user credentials are safe.

    Create a Verify Account Controller Function

    In this section, you’ll create a Verify Account controller function. This function verifies a user’s account using the OTP code sent to their email address.

    First, go to your authController.js file and add:

    <span class="hljs-built_in">exports</span>.verifyAccount = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) => {
      <span class="hljs-keyword">const</span> { email, otp } = req.body;
    
      <span class="hljs-keyword">if</span> (!email || !otp) {
        <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Email and OTP are required"</span>, <span class="hljs-number">400</span>));
      }
    
      <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email });
    
      <span class="hljs-keyword">if</span> (!user) {
        <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"No user found with this email"</span>, <span class="hljs-number">404</span>));
      }
    
      <span class="hljs-keyword">if</span> (user.otp !== otp) {
        <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Invalid OTP"</span>, <span class="hljs-number">400</span>));
      }
    
      <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Date</span>.now() > user.otpExpires) {
        <span class="hljs-keyword">return</span> next(
          <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"OTP has expired. Please request a new OTP."</span>, <span class="hljs-number">400</span>)
        );
      }
    
      user.isVerified = <span class="hljs-literal">true</span>;
      user.otp = <span class="hljs-literal">undefined</span>;
      user.otpExpires = <span class="hljs-literal">undefined</span>;
    
      <span class="hljs-keyword">await</span> user.save({ <span class="hljs-attr">validateBeforeSave</span>: <span class="hljs-literal">false</span> });
    
      <span class="hljs-comment">// ✅ Optionally return a response without logging in</span>
      res.status(<span class="hljs-number">200</span>).json({
        <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Email has been verified"</span>,
      });
    });
    

    Next, create a middleware function to authenticate the currently logged-in user.

    In your backend directory, create a new folder called middlewares. Inside the middlewares folder, create a file named isAuthenticated.js.

    Add the following code:

    <span class="hljs-comment">//isAuthenticated.js</span>
    <span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"jsonwebtoken"</span>);
    <span class="hljs-keyword">const</span> catchAsync = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/catchAsync"</span>);
    <span class="hljs-keyword">const</span> AppError = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../utils/appError"</span>);
    <span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../model/userModel"</span>);
    
    <span class="hljs-keyword">const</span> isAuthenticated = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) => {
      <span class="hljs-keyword">let</span> token;
    
      <span class="hljs-comment">// 1. Retrieve token from cookies or Authorization header</span>
      <span class="hljs-keyword">if</span> (req.cookies?.token) {
        token = req.cookies.token;
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (req.headers.authorization?.startsWith(<span class="hljs-string">"Bearer"</span>)) {
        token = req.headers.authorization.split(<span class="hljs-string">" "</span>)[<span class="hljs-number">1</span>];
      }
    
      <span class="hljs-keyword">if</span> (!token) {
        <span class="hljs-keyword">return</span> next(
          <span class="hljs-keyword">new</span> AppError(
            <span class="hljs-string">"You are not logged in. Please log in to access this resource."</span>,
            <span class="hljs-number">401</span>
          )
        );
      }
    
      <span class="hljs-comment">// 2. Verify token</span>
      <span class="hljs-keyword">let</span> decoded;
      <span class="hljs-keyword">try</span> {
        decoded = jwt.verify(token, process.env.JWT_SECRET);
      } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> next(
          <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Invalid or expired token. Please log in again."</span>, <span class="hljs-number">401</span>)
        );
      }
    
    <span class="hljs-comment">// 3. Confirm user still exists in database</span>
      <span class="hljs-keyword">const</span> currentUser = <span class="hljs-keyword">await</span> User.findById(decoded._id);
      <span class="hljs-keyword">if</span> (!currentUser) {
        <span class="hljs-keyword">return</span> next(
          <span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"User linked to this token no longer exists."</span>, <span class="hljs-number">401</span>)
        );
      }
    
      <span class="hljs-comment">// 4. Attach user info to request</span>
      req.user = currentUser;
      req.user = {
        <span class="hljs-attr">id</span>: currentUser.id,
        <span class="hljs-attr">name</span>: currentUser.name,
      };
    
      next();
    });
    
    <span class="hljs-built_in">module</span>.exports = isAuthenticated;
    <span class="hljs-string">``</span><span class="hljs-string">`
    Now, go to your `</span>userRouter.js<span class="hljs-string">` file and add the route for the verify account function:
    `</span><span class="hljs-string">``</span>
    <span class="hljs-keyword">const</span> { verifyAccount} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controller/authController"</span>);
    <span class="hljs-keyword">const</span> isAuthenticated = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/isAuthenticated"</span>);
    router.post(<span class="hljs-string">"/verify"</span>, isAuthenticated, verifyAccount);
    

    Here is what these two sets of code are doing:

    When a user sends a request to the /verify route, the isAuthenticated middleware runs first. It checks whether a valid token exists in the cookie or authorization header. If no token is found, it throws an error: You are not logged in. Please log in to access this resource.

    If a token is found, it verifies the token and checks if the associated user still exists. If not, another error is thrown: "User linked to this token no longer exists."

    If the user exists and the token is valid, their details are attached to the request (req.user). The request then proceeds to the verifyAccount controller, which handles OTP verification.

    You can test this endpoint using Postman with a POST request to: http://localhost:8000/api/v1/users/verify

    Verify-Account

    The image above shows that the verify token function is working well, and a status code of 200 is displayed.

    Login Function

    If you’ve reached this point, you’ve successfully signed up and verified a user’s account.

    Now it’s time to create the login function, which allows a verified user to access their account. Here’s how you can do that:

    Go to your authController.js file and create your login function by adding the following:

    <span class="hljs-built_in">exports</span>.login = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) => {
      <span class="hljs-keyword">const</span> { email, password } = req.body;
    
      <span class="hljs-comment">// 1. Validate email & password presence</span>
      <span class="hljs-keyword">if</span> (!email || !password) {
        <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Please provide email and password"</span>, <span class="hljs-number">400</span>));
      }
    
      <span class="hljs-comment">// 2. Check if user exists and include password</span>
      <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email }).select(<span class="hljs-string">"+password"</span>);
      <span class="hljs-keyword">if</span> (!user || !(<span class="hljs-keyword">await</span> user.correctPassword(password, user.password))) {
        <span class="hljs-keyword">return</span> next(<span class="hljs-keyword">new</span> AppError(<span class="hljs-string">"Incorrect email or password"</span>, <span class="hljs-number">401</span>));
      }
    
      <span class="hljs-comment">// 3. Create JWT token</span>
      <span class="hljs-keyword">const</span> token = signToken(user._id);
    
      <span class="hljs-comment">// 4. Configure cookie options</span>
      <span class="hljs-keyword">const</span> cookieOptions = {
        <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(
          <span class="hljs-built_in">Date</span>.now() +
            (<span class="hljs-built_in">parseInt</span>(process.env.JWT_COOKIE_EXPIRES_IN, <span class="hljs-number">10</span>) || <span class="hljs-number">90</span>) <span class="hljs-number">24</span> <span class="hljs-number">60</span> <span class="hljs-number">60</span> <span class="hljs-number">1000</span>
        ),
        <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-comment">// secure: process.env.NODE_ENV === "production",</span>
        <span class="hljs-comment">// sameSite: process.env.NODE_ENV === "production" ?</span>
        <span class="hljs-comment">//  "None" : "Lax",</span>
    
        <span class="hljs-comment">//set to false during or for local HTTP and cross-origin</span>
        <span class="hljs-attr">secure</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">sameSite</span>: <span class="hljs-string">"Lax"</span>,
      };
    
      <span class="hljs-comment">// 5. Send cookie</span>
      res.cookie(<span class="hljs-string">"token"</span>, token, cookieOptions);
    
    });
    

    if (!email || !password) {…} checks if the user actually provided both an email and a password. If not, it returns the error: Please provide email and password", 400.

    const user = await User.findOne({ email }).select("+password"); searches the database for a user with the provided email and explicitly includes the password field, since it’s normally hidden by default in the schema.

    if (!user || !(await user.correctPassword(…))) {…} checks if the user exists and if the password entered matches the one stored in the database (after hashing comparison). If either is wrong, it throws: Incorrect email or password.

    The line signToken(user._id) generates a JWT using the user’s unique ID. The cookieOptions object configures how the cookie behaves – it sets the cookie to expire after a specific number of days defined in the .env file, marks it as httpOnly to prevent JavaScript access for security, sets secure to false since the app is currently in development, and uses sameSite: "Lax" to allow cross-origin requests during local testing.

    Finally, res.cookie(...) sends the token as a cookie attached to the HTTP response, enabling the client to store the token for authentication purposes.

    From the code above, you may have noticed that the password stored in the database is hashed for security reasons. This means it looks completely different from the user’s password when logging in. So, even if a user types in the correct password, it won’t match the stored hash directly through a simple comparison.

    To fix this, you need to compare the entered password with the hashed one using the bcryptjs package.

    Head over to your userModel.js file and create a method that handles password comparison. This method will take the plain text password provided by the user and compare it to the hashed password stored in the database.

    <span class="hljs-comment">//userModel.js</span>
    <span class="hljs-comment">//create a method responsible for comparing the password stored in the database</span>
    
    userSchema.methods.correctPassword = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">password, userPassword</span>) </span>{
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> bcrypt.compare(password, userPassword);
    };
    

    This correctPassword method uses bcrypt.compare(), which internally hashes the plain password and checks if it matches the stored hashed version. This ensures that login validation works correctly and securely, even though the actual password is not stored in plain text.

    Next, add the Login functionality to the userRouter.js file.

    <span class="hljs-keyword">const</span> {login} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controller/authController"</span>);
    <span class="hljs-keyword">const</span> isAuthenticated = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/isAuthenticated"</span>);
    
    router.post(<span class="hljs-string">"/login"</span>, login);
    

    You can test this endpoint using Postman with a POST request to: http://localhost:8000/api/v1/users/login

    Logout Function

    At this point, you can implement the logout function to end a user’s session securely. To do this, navigate to your authController.js file and add the following function:

    <span class="hljs-comment">//creating a log out function</span>
    <span class="hljs-built_in">exports</span>.logout = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) => {
      res.cookie(<span class="hljs-string">"token"</span>, <span class="hljs-string">"loggedout"</span>, {
        <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-number">0</span>),
        <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">"production"</span>,
      });
    
      res.status(<span class="hljs-number">200</span>).json({
        <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Logged out successfully"</span>,
      });
    });
    

    This function works by overwriting the authentication cookie named token with the value "loggedout" and setting its expiration time to the past using new Date(0). This effectively invalidates the cookie and removes it from the browser.

    The httpOnly: true flag ensures that the cookie cannot be accessed via JavaScript, which protects it from XSS attacks, while the secure flag ensures that the cookie is only sent over HTTPS in a production environment. Once the cookie is cleared, a success response is returned with the message “Logged out successfully” to confirm the action.

    Next, add the logout functionality to your route:

    <span class="hljs-keyword">const</span> {logout} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controller/authController"</span>);
    <span class="hljs-keyword">const</span> isAuthenticated = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/isAuthenticated"</span>);
    
    router.post(<span class="hljs-string">"/logout"</span>, logout);
    

    Then, head to Postman to test your logout function and see if it works.

    Frontend Setup

    Now that your backend is up and running, you can integrate it into your frontend application.

    First, navigate to the frontend directory using the command cd Frontend.

    Create a new folder in the src folder where your authentication-related files will live. Depending on your preference or app structure, you can name it something like auth or pages. Then, create a new file called NewUser. js. This file will handle user signup functionality.

    <span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
    <span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
    <span class="hljs-keyword">import</span> { Link, useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
    <span class="hljs-keyword">import</span> { Loader } <span class="hljs-keyword">from</span> <span class="hljs-string">'lucide-react'</span>;
    <span class="hljs-keyword">import</span> { useDispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-redux'</span>;
    <span class="hljs-keyword">import</span> { setAuthUser, setPendingEmail } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../store/authSlice'</span>;
    
    <span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL;
    
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">NewUser</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> dispatch = useDispatch();
      <span class="hljs-keyword">const</span> navigate = useNavigate();
    
      <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);
    
      <span class="hljs-keyword">const</span> [formData, setFormData] = useState({
        <span class="hljs-attr">username</span>: <span class="hljs-string">''</span>,
        <span class="hljs-attr">email</span>: <span class="hljs-string">''</span>,
        <span class="hljs-attr">password</span>: <span class="hljs-string">''</span>,
        <span class="hljs-attr">passwordConfirm</span>: <span class="hljs-string">''</span>,
      });
    
      <span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =></span> {
        <span class="hljs-keyword">const</span> { name, value } = e.target;
        setFormData(<span class="hljs-function">(<span class="hljs-params">prev</span>) =></span> ({ ...prev, [name]: value }));
      };
    
      <span class="hljs-keyword">const</span> submitHandler = <span class="hljs-keyword">async</span> (e) => {
        e.preventDefault();
        setLoading(<span class="hljs-literal">true</span>);
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">`<span class="hljs-subst">${API_URL}</span>/users/signup`</span>, formData, {
            <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span>,
          });
          <span class="hljs-keyword">const</span> user = response.data.data.user;
          dispatch(setAuthUser(user));
          dispatch(setPendingEmail(formData.email)); <span class="hljs-comment">// Save email for OTP</span>
          navigate(<span class="hljs-string">'/verifyAcct'</span>); <span class="hljs-comment">// Navigate to OTP verification page</span>
        } <span class="hljs-keyword">catch</span> (error) {
          alert(error.response?.data?.message || <span class="hljs-string">'Signup failed'</span>);
        } <span class="hljs-keyword">finally</span> {
          setLoading(<span class="hljs-literal">false</span>);
        }
      };
    
      <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
    // visit the frontend Github repository to see the remaining code for the OTP Verification
    
    https://github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/Join/NewUser.jsx
        <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
      );
    }
    
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> NewUser;
    

    The code above renders a signup form with fields for username, email, password and passwordConfirm. When the user submits the form, the frontend sends a POST request to the backend’s /users/signup endpoint using Axios. The withCredentials: true option ensures cookies like the auth token are properly set by the backend.

    If the signup is successful, the user data is dispatched into Redux using setAuthUser(), and their email is saved with setPendingEmail() so it can be used during OTP verification. Then, the user is redirected to the /verifyAcct route, where they can enter their OTP.

    Frontend-Sign-Up

    OTP Verification Page

    The OTP verification page is the next step in the user authentication process. Once a user signs up, they are redirected to enter the 4-digit OTP sent to their email. This verifies their account before allowing login access.

    <span class="hljs-keyword">import</span> React, { useState, useRef, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
    <span class="hljs-keyword">import</span> { useSelector, useDispatch } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-redux'</span>;
    <span class="hljs-keyword">import</span> { useNavigate, Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
    <span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
    <span class="hljs-keyword">import</span> { clearPendingEmail } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../../store/authSlice'</span>;
    
    <span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL || <span class="hljs-string">'http://localhost:5000/api'</span>; <span class="hljs-comment">// adjust as needed</span>
    
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">VerifyAcct</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> [code, setCode] = useState([<span class="hljs-string">''</span>, <span class="hljs-string">''</span>, <span class="hljs-string">''</span>, <span class="hljs-string">''</span>]);
      <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);
      <span class="hljs-keyword">const</span> [resendLoading, setResendLoading] = useState(<span class="hljs-literal">false</span>);
      <span class="hljs-keyword">const</span> [timer, setTimer] = useState(<span class="hljs-number">60</span>);
    
      <span class="hljs-keyword">const</span> inputsRef = useRef([]);
      <span class="hljs-keyword">const</span> dispatch = useDispatch();
      <span class="hljs-keyword">const</span> navigate = useNavigate();
      <span class="hljs-keyword">const</span> email = useSelector(<span class="hljs-function">(<span class="hljs-params">state</span>) =></span> state.auth.pendingEmail);
    
      useEffect(<span class="hljs-function">() =></span> {
        <span class="hljs-keyword">let</span> interval;
        <span class="hljs-keyword">if</span> (timer > <span class="hljs-number">0</span>) {
          interval = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =></span> setTimer(<span class="hljs-function">(<span class="hljs-params">prev</span>) =></span> prev - <span class="hljs-number">1</span>), <span class="hljs-number">1000</span>);
        }
        <span class="hljs-keyword">return</span> <span class="hljs-function">() =></span> <span class="hljs-built_in">clearInterval</span>(interval);
      }, [timer]);
    
      <span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">value, index</span>) =></span> {
        <span class="hljs-keyword">if</span> (!<span class="hljs-regexp">/^d*$/</span>.test(value)) <span class="hljs-keyword">return</span>;
        <span class="hljs-keyword">const</span> newCode = [...code];
    <span class="hljs-comment">// visit the frontend Github repository to see the remaining code for the OTP Verification</span>
    https:<span class="hljs-comment">//github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/login/VerifyAcct.jsx</span>
    }
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> VerifyAcct;
    

    Here’s what the code does:

    The OTP is stored as an array of 4 characters ([‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘]). Each box only accepts digits, and focus automatically moves to the next input as the user types in the digit. The focus returns to the previous input box if the user presses the backspace button on an empty box.

    When the OTP has been added and the form is submitted, the 4-digit code is joined into a string and an HTTP POST request is made to the backend /user/verify/ endpoint along with the stored email and OTP. If the verification is successful, the user is alerted and redirected to the login page, and if not, an error is shown.

    Frontend-OTP

    Log In

    Now you can create the login interface for your application. First, create a Login.jsx file and input the code:

    <span class="hljs-comment">//Login.Jsx</span>
    
    <span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
    <span class="hljs-keyword">import</span> { Link, useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
    <span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
    
    <span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL || <span class="hljs-string">'https://telehealth-backend-2m1f.onrender.com/api/v1'</span>;
    
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Join</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> [formData, setFormData] = useState({ <span class="hljs-attr">email</span>: <span class="hljs-string">''</span>, <span class="hljs-attr">password</span>: <span class="hljs-string">''</span> });
      <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">false</span>);
      <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-string">''</span>);
      <span class="hljs-keyword">const</span> navigate = useNavigate();
    
      <span class="hljs-keyword">const</span> handleChange = <span class="hljs-function">(<span class="hljs-params">e</span>) =></span> {
        setFormData({ ...formData, [e.target.name]: e.target.value });
      };
    
      <span class="hljs-keyword">const</span> handleLogin = <span class="hljs-keyword">async</span> (e) => {
        e.preventDefault();
        setLoading(<span class="hljs-literal">true</span>);
        setError(<span class="hljs-string">''</span>);
    
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">`<span class="hljs-subst">${API_URL}</span>/users/login`</span>, formData, {
            <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span>,
          });
    
          <span class="hljs-keyword">if</span> (res.data.status === <span class="hljs-string">'success'</span>) {
            <span class="hljs-keyword">const</span> { token, user, streamToken } = res.data;
    
            <span class="hljs-comment">// Save to localStorage</span>
            <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'authToken'</span>, token);
            <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'user'</span>, <span class="hljs-built_in">JSON</span>.stringify(user));
            <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'streamToken'</span>, streamToken);
    
            navigate(<span class="hljs-string">'/dashboard'</span>);
          }
        } <span class="hljs-keyword">catch</span> (err) {
          <span class="hljs-built_in">console</span>.error(err);
          setError(
            err.response?.data?.message || <span class="hljs-string">'Something went wrong. Please try again.'</span>
          );
        } <span class="hljs-keyword">finally</span> {
          setLoading(<span class="hljs-literal">false</span>);
        }
      };
    
      <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>
    {// visit the frontend Github repository to see the remaining code for the OTP Verification
    https://github.com/Derekvibe/Telehealth_Frontend/blob/main/src/pages/Auth/login/Login.jsx
     <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
    );
    }
    

    The Export default Join; component allows a registered and verified user to log into your application using their email and password. It handles form submission, talks to the backend, and securely stores user data if login is successful.

    handleChange() updates the email or password field as the user types.

    handleLogin() is triggered when the login form is submitted. When the login button is triggered, it sends a Post request to /users/login with the form data, which includes {withCredentials: true} to enable cookie handling.

    If login is successful, it extracts the JWT token, user data, and Stream Chat token from the response and stores them in the localStorage so the user stays logged in across sessions. Then it redirects the user to the dashboard page using navigate(‘/dashboard’).

    Frontend-Login

    Set Up the Frontend Route

    Just as you set up the backend route, you have to do the same for the frontend.

    Head to App.jsx. Before adding the route, make sure you have installed the react-router-dom package. If not, run this command in the frontend terminal:

    npm install react-router-dom

    Then, add the command to your App.jsx file:

    <span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
    <span class="hljs-keyword">import</span> { createBrowserRouter, RouterProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;
    <span class="hljs-keyword">import</span> HomeIndex <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Home/HomeIndex'</span>;
    <span class="hljs-keyword">import</span> Hero <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Home/Hero'</span>;
    
    <span class="hljs-comment">//Authentication Section</span>
    <span class="hljs-keyword">import</span> NewUser <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Auth/Join/NewUser'</span>;
    <span class="hljs-keyword">import</span> Login <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Auth/login/Login'</span>
    <span class="hljs-keyword">import</span> VerifyAcct <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Auth/login/VerifyAcct'</span>;
    
    <span class="hljs-comment">// Dashboard</span>
    <span class="hljs-keyword">import</span> Dashboard <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Dashboard/Dashboard'</span>;
    <span class="hljs-keyword">import</span> VideoStream <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/VideoStream'</span>;
    
    <span class="hljs-keyword">const</span> router = createBrowserRouter([
      {
        <span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>,
        <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag"><<span class="hljs-name">HomeIndex</span> /></span></span>,
        children: [
          { <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag"><<span class="hljs-name">Hero</span> /></span></span> }
        ],
      },
    
      {
        <span class="hljs-attr">path</span>: <span class="hljs-string">'signup'</span>,
        <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag"><<span class="hljs-name">NewUser</span> /></span></span>,
        children: [
          { <span class="hljs-attr">index</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag"><<span class="hljs-name">NewUser</span> /></span></span> }
        ],
      },
    
      {
        <span class="hljs-attr">path</span>: <span class="hljs-string">'login'</span>,
        <span class="hljs-attr">element</span>: <span class="xml"><span class="hljs-tag"><<span class="hljs-name">Login</span> /></span></span>,
        children: [
          {<span class="hljs-attr">index</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">element</span>:<span class="xml"><span class="hljs-tag"><<span class="hljs-name">Login</span> /></span></span>}
        ]
      },
    
    ]);
    {<span class="hljs-comment">// visit the frontend Github repository to see the remaining code for the OTP Verification</span>
    <span class="hljs-attr">https</span>:<span class="hljs-comment">//github.com/Derekvibe/Telehealth_Frontend/blob/main/src/App.jsx}</span>
    
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'border border-red-700 w-full min-w-[100vw] min-h-[100vh]'</span>></span>
          <span class="hljs-tag"><<span class="hljs-name">RouterProvider</span> <span class="hljs-attr">router</span>=<span class="hljs-string">{router}</span> /></span>
        <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
      );
    }
    
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
    

    Stream Chat and Video Integration

    Before proceeding to the dashboard, let’s integrate the Stream Chat and Video functionality into the project.

    First, create a free Stream account, start a new project in your dashboard, and get your APP KEY and API_SECRET.

    STREAM_API_KEY=your_app_key
    STREAM_API_SECRET=your_api_secret
    

    Watch the Stream Chat React Quick Start Guide to see how you can set it up.

    Next, store your Stream APP KEY and API_SECRET in your .env.

    Install Stream Packages (Frontend)

    Now, install the Stream Chat and Video packages in your terminal.

    npm install stream-chat stream-chat-react
    npm install @stream-io/video-react-sdk
    npm install @stream-io/stream-chat-css
    

    Stream Token Handler

    First, create a new file in your frontend Src directory and name it. In this example, it’s StreamContext.jsx. This file sets up a context to fetch and manage the Stream Chat token on login and includes logout functionality.

    <span class="hljs-keyword">import</span> React, { createContext, useContext, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
    <span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;
    
    <span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL || <span class="hljs-string">'https://telehealth-backend-2m1f.onrender.com/api/v1'</span>;
    
    <span class="hljs-comment">// 1. Create the context</span>
    <span class="hljs-keyword">const</span> StreamContext = createContext();
    
    <span class="hljs-comment">// 2. Provider component</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> StreamProvider = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =></span> {
      <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);
      <span class="hljs-keyword">const</span> [token, setToken] = useState(<span class="hljs-literal">null</span>);
    
      useEffect(<span class="hljs-function">() =></span> {
        <span class="hljs-keyword">const</span> fetchToken = <span class="hljs-keyword">async</span> () => {
          <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${API_URL}</span>/stream/get-token`</span>, {
              <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span>,
            });
    
            <span class="hljs-keyword">if</span> (res.data?.user && res.data?.token) {
              setUser(res.data.user);
              setToken(res.data.token);
              <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Stream user/token:"</span>, res.data);
            } <span class="hljs-keyword">else</span> {
              <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Token or user missing in response:"</span>, res.data);
            }
          } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error fetching Stream token:"</span>, error);
          }
        };
    
        fetchToken();
      }, []);
    
      <span class="hljs-comment">//Log out Functionality</span>
      <span class="hljs-keyword">const</span> logout = <span class="hljs-keyword">async</span> () => {
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">`<span class="hljs-subst">${API_URL}</span>/users/logout`</span>, {},
            {
              <span class="hljs-attr">withCredentials</span>: <span class="hljs-literal">true</span>
            });
    
          <span class="hljs-comment">// Clear localStorage</span>
          <span class="hljs-built_in">localStorage</span>.removeItem(<span class="hljs-string">'authToken'</span>);
          <span class="hljs-built_in">localStorage</span>.removeItem(<span class="hljs-string">'user'</span>);
          <span class="hljs-built_in">localStorage</span>.removeItem(<span class="hljs-string">'streamToken'</span>);
    
          <span class="hljs-comment">// Clear context</span>
          setUser(<span class="hljs-literal">null</span>);
          setToken(<span class="hljs-literal">null</span>);
        } <span class="hljs-keyword">catch</span> (error) {
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Logout failed"</span>, error);
        }
      };
    
      <span class="hljs-comment">// Expose Logout with capital L</span>
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag"><<span class="hljs-name">StreamContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">user</span>, <span class="hljs-attr">token</span>, <span class="hljs-attr">Logout:logout</span> }}></span>
          {children}
        <span class="hljs-tag"></<span class="hljs-name">StreamContext.Provider</span>></span></span>
      );
    };
    
    <span class="hljs-comment">// 3. Custom hook for easy access</span>
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useStream = <span class="hljs-function">() =></span> useContext(StreamContext);
    

    The code above creates a StreamContext using React’s Context API. In the useEffect section, it makes a GET request to /stream/get-token to fetch the authenticated user and their Stream token. Then it stores them in user and token states. It also provides the user/token through the context so that any component that needs it can make use of it.

    Finally, it adds a Logout method that hits the logout endpoint and clears all stored auth data from localStorage.

    Next, open your main.jsx and wrap your entire application with the StreamProvider so all child components can access the Stream context.

    <span class="hljs-comment">// main.jsx</span>
    <span class="hljs-keyword">import</span> { createRoot } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
    <span class="hljs-keyword">import</span> { StrictMode } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
    <span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;
    <span class="hljs-keyword">import</span> { StreamProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/StreamContext'</span>;
    
    createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>)).render(
      <span class="xml"><span class="hljs-tag"><<span class="hljs-name">StrictMode</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">StreamProvider</span>></span>
          <span class="hljs-tag"><<span class="hljs-name">App</span> /></span>
        <span class="hljs-tag"></<span class="hljs-name">StreamProvider</span>></span>
      <span class="hljs-tag"></<span class="hljs-name">StrictMode</span>></span></span>
    );
    

    Set Up Stream API

    After successfully creating the streamContent, the next step is to set up the Stream API. This will be the endpoint from which the user ID and user Stream token can be generated and fetched during login.

    To set it up, navigate to your backend directory by running cd Backend in your terminal. Then install the Stream package using the command:

    npm install getstream
    npm install stream-chat stream-chat-react
    

    Open your .env file and add your Stream API KEY and API_SECRET:

    STREAM_API_KEY=your_app_key
    STREAM_API_SECRET=your_api_secret
    

    Next, open your authController.js and create your Stream Chat logic:

    <span class="hljs-comment">//Initialize the Stream Client</span>
    <span class="hljs-keyword">const</span> {StreamChat} = <span class="hljs-built_in">require</span>(<span class="hljs-string">"stream-chat"</span>);
    <span class="hljs-keyword">const</span> streamClient = StreamChat.getInstance(
      process.env.STREAM_API_KEY,
      process.env.STREAM_API_SECRET
    );
    
    <span class="hljs-comment">// Modifies the `createSendToken to include `streamToken`</span>
    <span class="hljs-keyword">const</span> createSendToken = <span class="hljs-function">(<span class="hljs-params">user, statusCode, res, message</span>) =></span> {
    ……
    <span class="hljs-keyword">const</span> streamToken = streamClient.createToken(user._id.toString());
    
      <span class="hljs-comment">//structure of the cookie response when sent to the user</span>
      res.status(statusCode).json({
        <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
        message,
        token,
        streamToken,
        <span class="hljs-attr">data</span>: {
          <span class="hljs-attr">user</span>: {
            <span class="hljs-attr">id</span>: user._id.toString(),
            <span class="hljs-attr">name</span>: user.username,
          },
        },
      });
    };
    
    <span class="hljs-comment">//login functionality</span>
    <span class="hljs-built_in">exports</span>.login = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) => {
     {…………………..}
    
    <span class="hljs-comment">// Generate Stream token</span>
      <span class="hljs-keyword">await</span> streamClient.upsertUser({
        <span class="hljs-attr">id</span>: user._id.toString(),
        <span class="hljs-attr">name</span>: user.username,
      });
      <span class="hljs-keyword">const</span> streamToken = streamClient.createToken(user._id.toString());
    
    user.password = <span class="hljs-literal">undefined</span>;
    
      res.status(<span class="hljs-number">200</span>).json({
        <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Login successful"</span>,
        token,
        <span class="hljs-attr">user</span>: {
          <span class="hljs-attr">id</span>: user._id.toString(),
          <span class="hljs-attr">name</span>: user.username,
        },
        streamToken,
      });
    

    streamRoutes Endpoint

    Next, create an endpoint from which the Stream token can be called. To do this, go to your routes folder and create a new file called streamRoutes.js. In streamRoutes.js, add the command:

    <span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
    <span class="hljs-keyword">const</span> { StreamChat } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"stream-chat"</span>);
    
    <span class="hljs-keyword">const</span> protect = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/protect"</span>);
    
    <span class="hljs-keyword">const</span> router = express.Router();
    
    <span class="hljs-keyword">const</span> apiKey = process.env.STREAM_API_KEY;
    <span class="hljs-keyword">const</span> apiSecret = process.env.STREAM_API_SECRET;
    
    <span class="hljs-keyword">if</span> (!apiKey || !apiSecret) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(
        <span class="hljs-string">"Missing Stream credentials. Check your environment variables."</span>
      );
    }
    
    <span class="hljs-keyword">const</span> streamClient = StreamChat.getInstance(apiKey, apiSecret);
    
    router.get(<span class="hljs-string">"/get-token"</span>, protect, <span class="hljs-keyword">async</span> (req, res) => {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> { id, username } = req.user || {};
        <span class="hljs-built_in">console</span>.log(req.user.id, <span class="hljs-string">"User"</span>);
        <span class="hljs-comment">// TRY LOGGING THE ID AND NAME FROM YOUR REQUEST FIRST</span>
    
        <span class="hljs-keyword">if</span> (!id || !username) {
          <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Invalid user data"</span> });
        }
    
        <span class="hljs-comment">// const userId = _id.toString();</span>
        <span class="hljs-keyword">const</span> user = { id, username };
    
        <span class="hljs-comment">// Ensure user exists in Stream backend</span>
        <span class="hljs-keyword">await</span> streamClient.upsertUser(user);
    
        <span class="hljs-comment">// Add user to my_general_chat channel</span>
        <span class="hljs-keyword">const</span> channel = streamClient.channel(<span class="hljs-string">"messaging"</span>, <span class="hljs-string">"my_general_chat"</span>);
        <span class="hljs-keyword">await</span> channel.addMembers([id]);
    
    
        <span class="hljs-comment">// Generate token</span>
        <span class="hljs-keyword">const</span> token = streamClient.createToken(id);
        res.status(<span class="hljs-number">200</span>).json({ token, user });
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Stream token generation error:"</span>, error);
        res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to generate Stream token"</span> });
      }
    });
    
    <span class="hljs-comment">/**
     * @route   POST /api/stream/token
     * @desc    Generate a Stream token for any userId from request body (no auth)
     * @access  Public
     */</span>
    router.post(<span class="hljs-string">"/token"</span>, <span class="hljs-keyword">async</span> (req, res) => {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> { userId, name } = req.body;
    
        <span class="hljs-keyword">if</span> (!userId) {
          <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"userId is required"</span> });
        }
    
        <span class="hljs-keyword">const</span> userName = name || <span class="hljs-string">"Anonymous"</span>;
        <span class="hljs-keyword">const</span> user = { <span class="hljs-attr">id</span>: userId, <span class="hljs-attr">name</span>: userName };
    
        <span class="hljs-keyword">await</span> streamClient.upsertUser(user);
    
        <span class="hljs-comment">// Add user to my_general_chat channel</span>
        <span class="hljs-keyword">const</span> channel = streamClient.channel(<span class="hljs-string">"messaging"</span>, <span class="hljs-string">"my_general_chat"</span>);
        <span class="hljs-keyword">await</span> channel.addMembers([userId]);
    
    
        <span class="hljs-keyword">const</span> token = streamClient.createToken(userId);
    
        res.status(<span class="hljs-number">200</span>).json({
          token,
          <span class="hljs-attr">user</span>: {
            <span class="hljs-attr">id</span>: userId,
            <span class="hljs-attr">name</span>: name,
            <span class="hljs-attr">role</span>: <span class="hljs-string">"admin"</span>,
            <span class="hljs-attr">image</span>: <span class="hljs-string">`https://getstream.io/random_png/?name=<span class="hljs-subst">${name}</span>`</span>,
          },
        });
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Public token generation error:"</span>, error);
        res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to generate token"</span> });
      }
    });
    
    <span class="hljs-built_in">module</span>.exports = router;
    

    User Logout Endpoint

    Go to your authController.js and create a functionality that handles logging out the user:

    <span class="hljs-built_in">exports</span>.logout = catchAsync(<span class="hljs-keyword">async</span> (req, res, next) => {
      res.cookie(<span class="hljs-string">"token"</span>, <span class="hljs-string">"loggedout"</span>, {
        <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-number">0</span>),
        <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">"production"</span>,
      });
    
      res.status(<span class="hljs-number">200</span>).json({
        <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Logged out successfully"</span>,
      });
    });
    

    Then register your logout route to your userRouters.js:

    <span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
    <span class="hljs-keyword">const</span> {logout}= <span class="hljs-built_in">require</span>(<span class="hljs-string">"../controller/authController"</span>);
    <span class="hljs-keyword">const</span> isAuthenticated = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../middlewares/isAuthenticated"</span>);
    
    
    router.post(<span class="hljs-string">"/logout"</span>, isAuthenticated, logout);
    
    <span class="hljs-built_in">module</span>.exports = router;
    

    Chat and Video Function (Frontend)

    After setting up your backend Stream API, the last task is setting up chat and video in your frontend application.

    Dashboard.jsx

    Create a new file Dashboard.jsx in your frontend directory. This is where you will set up your Stream and video function.

    <span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
    <span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;
    <span class="hljs-keyword">import</span> {
      Chat,
      Channel,
      ChannelHeader,
      MessageInput,
      MessageList,
      Thread,
      Window,
      useCreateChatClient,
    } <span class="hljs-keyword">from</span> <span class="hljs-string">"stream-chat-react"</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">"stream-chat-react/dist/css/v2/index.css"</span>;
    <span class="hljs-keyword">import</span> { useStream } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/StreamContext"</span>;
    <span class="hljs-keyword">import</span> VideoStream <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/VideoStream"</span>;
    <span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
    
    
    
    <span class="hljs-keyword">const</span> apiKey = <span class="hljs-keyword">import</span>.meta.env.VITE_STREAM_API_KEY;
    
    <span class="hljs-keyword">const</span> API_URL = <span class="hljs-keyword">import</span>.meta.env.VITE_API_URL || <span class="hljs-string">'https://telehealth-backend-2m1f.onrender.com/api/v1'</span>;
    
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> [channel, setChannel] = useState(<span class="hljs-literal">null</span>);
      <span class="hljs-keyword">const</span> [clientReady, setClientReady] = useState(<span class="hljs-literal">false</span>);
      <span class="hljs-keyword">const</span> navigate = useNavigate();
    
      <span class="hljs-comment">// const ChatComponent = () => {</span>
        <span class="hljs-keyword">const</span> { user, token, Logout } = useStream();
    
        <span class="hljs-comment">// Always call the hook</span>
        <span class="hljs-keyword">const</span> chatClient = useCreateChatClient({
          apiKey,
          <span class="hljs-attr">tokenOrProvider</span>: token,
          <span class="hljs-attr">userData</span>: user?.id ? { <span class="hljs-attr">id</span>: user.id } : <span class="hljs-literal">undefined</span>,
        });
    
      <span class="hljs-comment">// Debug: See when user/token is ready</span>
      useEffect(<span class="hljs-function">() =></span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Stream user:"</span>, user);
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Stream token:"</span>, token);
      }, [user, token]);
    
        <span class="hljs-comment">// Connect user to Stream</span>
        useEffect(<span class="hljs-function">() =></span> {
          <span class="hljs-keyword">const</span> connectUser = <span class="hljs-keyword">async</span> () => {
            <span class="hljs-keyword">if</span> (!chatClient || !user || !token || !user?.id) {
              <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"Missing chat setup data:"</span>, { chatClient, token, user });
              <span class="hljs-keyword">return</span>;
            }
    
    
            <span class="hljs-keyword">try</span> {
              <span class="hljs-keyword">await</span> chatClient.connectUser(
                {
                  <span class="hljs-attr">id</span>: user.id,
                  <span class="hljs-attr">name</span>: user.name || <span class="hljs-string">"Anonymous"</span>,
                  <span class="hljs-attr">image</span>:
                    user.image ||
                    <span class="hljs-string">`https://getstream.io/random_png/?name=<span class="hljs-subst">${user.name || <span class="hljs-string">"user"</span>}</span>`</span>,
                },
                token
              );
    
              <span class="hljs-keyword">const</span> newChannel = chatClient.channel(<span class="hljs-string">"messaging"</span>, <span class="hljs-string">"my_general_chat"</span>, {
                <span class="hljs-attr">name</span>: <span class="hljs-string">"General Chat"</span>,
                <span class="hljs-attr">members</span>: [user.id],
              });
    
              <span class="hljs-keyword">await</span> newChannel.watch();
              setChannel(newChannel);
              setClientReady(<span class="hljs-literal">true</span>);
            } <span class="hljs-keyword">catch</span> (err) {
              <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error connecting user:"</span>, err);
            }
          };
    
          connectUser();
        }, [chatClient, user, token]);
    
        <span class="hljs-keyword">const</span> handleVideoCallClick = <span class="hljs-function">() =></span> {
          navigate(<span class="hljs-string">"/videoCall"</span>);
      };
    
      <span class="hljs-keyword">const</span> handleLogout = <span class="hljs-keyword">async</span> () => {
        <span class="hljs-keyword">await</span> Logout();
        navigate(<span class="hljs-string">"/login"</span>);
      }
    
      <span class="hljs-keyword">if</span> (!user || !token) {
        <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red-600"</span>></span>User or token not ready.<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
      }
    
      <span class="hljs-keyword">if</span> (!clientReady || !channel) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>Loading chat...<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
    
    
      <span class="hljs-keyword">return</span> (
    { checkout the github repo}
                <ChannelHeader />
                <span class="xml"><span class="hljs-tag"><<span class="hljs-name">MessageList</span> /></span></span>
                <span class="xml"><span class="hljs-tag"><<span class="hljs-name">MessageInput</span> /></span></span>
              </Window>
              <span class="xml"><span class="hljs-tag"><<span class="hljs-name">Thread</span> /></span></span>
            </Channel>
          </Chat>
    
    
          </div>
    
        );
      }
    
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
    

    Video Setup

    You’ll now set up the video function for your frontend. To do so, create a new file VideoStream.jsx and add the command:

    <span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
    <span class="hljs-keyword">import</span> { StreamVideoClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-client"</span>;
    <span class="hljs-keyword">import</span> { StreamVideo, StreamCall } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stream-io/video-react-sdk"</span>;
    <span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
    
    
    <span class="hljs-keyword">import</span> { useStream } <span class="hljs-keyword">from</span> <span class="hljs-string">"./StreamContext"</span>;
    <span class="hljs-keyword">import</span> { MyUILayout } <span class="hljs-keyword">from</span> <span class="hljs-string">"./MyUILayout"</span>;
    
    
    <span class="hljs-keyword">const</span> apiKey = <span class="hljs-keyword">import</span>.meta.env.VITE_STREAM_API_KEY;
    
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">VideoStream</span>(<span class="hljs-params"></span>) </span>{
    
      <span class="hljs-keyword">const</span> [client, setClient] = useState(<span class="hljs-literal">null</span>);
        <span class="hljs-keyword">const</span> [call, setCall] = useState(<span class="hljs-literal">null</span>);
      <span class="hljs-keyword">const</span> { user, token } = useStream();
      <span class="hljs-keyword">const</span> navigate = useNavigate();
    
      useEffect(<span class="hljs-function">() =></span> {
    
        <span class="hljs-keyword">let</span> clientInstance;
        <span class="hljs-keyword">let</span> callInstance;
    
    
        <span class="hljs-keyword">const</span> setup = <span class="hljs-keyword">async</span> () => {
          <span class="hljs-keyword">if</span> (!apiKey || !user || !token) <span class="hljs-keyword">return</span>;
    
          clientInstance = <span class="hljs-keyword">new</span> StreamVideoClient({ apiKey, user, token });
    
          callInstance = clientInstance.call(<span class="hljs-string">"default"</span>, user.id); <span class="hljs-comment">// Use user.id as callId</span>
    
    
          <span class="hljs-keyword">await</span> callInstance.join({ <span class="hljs-attr">create</span>: <span class="hljs-literal">true</span> });
    
          setClient(clientInstance);
          setCall(callInstance);
        };
    
        setup();
    
        <span class="hljs-keyword">return</span> <span class="hljs-function">() =></span> {
          <span class="hljs-keyword">if</span> (callInstance) callInstance.leave();
          <span class="hljs-keyword">if</span> (clientInstance) clientInstance.disconnectUser();
    
        };
      }, [user, token]);
    
      <span class="hljs-keyword">const</span> handleLeaveCall = <span class="hljs-keyword">async</span> () => {
        <span class="hljs-keyword">if</span> (call) <span class="hljs-keyword">await</span> call.leave();
        <span class="hljs-keyword">if</span> (client) <span class="hljs-keyword">await</span> client.disconnectUser();
    
        setCall(<span class="hljs-literal">null</span>);
        setClient(<span class="hljs-literal">null</span>);
    
        navigate(<span class="hljs-string">"/dashboard"</span>); <span class="hljs-comment">// or any other route</span>
      };
    
      <span class="hljs-keyword">if</span> (!apiKey) <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>Missing Stream API Key<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
    
      <span class="hljs-keyword">if</span> (!client || !call)
        <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center justify-center h-screen text-xl font-semibold"</span>></span>
        Connecting to the video call...
      <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
        );
    
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative h-screen w-full p-2 sm:p-4 bg-gray-50"</span>></span>
          <span class="hljs-tag"><<span class="hljs-name">StreamVideo</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{client}</span>></span>
            <span class="hljs-tag"><<span class="hljs-name">StreamCall</span> <span class="hljs-attr">call</span>=<span class="hljs-string">{call}</span>></span>
              <span class="hljs-tag"><<span class="hljs-name">MyUILayout</span> /></span>
            <span class="hljs-tag"></<span class="hljs-name">StreamCall</span>></span>
          <span class="hljs-tag"></<span class="hljs-name">StreamVideo</span>></span>
    
          <span class="hljs-tag"><<span class="hljs-name">button</span>
            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleLeaveCall}</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">"absolute top-2 right-2 sm:top-4 sm:right-4 bg-red-600 text-white text-sm sm:text-base px-3 sm:px-4 py-1.5 sm:py-2 rounded-lg shadow hover:bg-red-700 transition"</span>
          ></span>
            Leave Call
          <span class="hljs-tag"></<span class="hljs-name">button</span>></span>
        <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
        );
      }
    
    <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> VideoStream;
    
    <span class="hljs-comment">//MYUILayout.jsx</span>
    <span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
    <span class="hljs-keyword">import</span> {
      useCall,
      useCallStateHooks,
      CallingState,
    } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stream-io/video-react-sdk'</span>;
    
    <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyUILayout</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">const</span> call = useCall();
      <span class="hljs-keyword">const</span> { useCallCallingState, useParticipantCount } = useCallStateHooks();
      <span class="hljs-keyword">const</span> callingState = useCallCallingState();
      <span class="hljs-keyword">const</span> participantCount = useParticipantCount();
    
      <span class="hljs-keyword">if</span> (callingState !== CallingState.JOINED) {
        <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span>></span>Joining call...<span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>;
      }
    
      <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> '<span class="hljs-attr">1rem</span>', <span class="hljs-attr">fontSize:</span> '<span class="hljs-attr">1.2rem</span>' }}></span>
          ✅ Call "<span class="hljs-tag"><<span class="hljs-name">strong</span>></span>{call?.id}<span class="hljs-tag"></<span class="hljs-name">strong</span>></span>" has <span class="hljs-tag"><<span class="hljs-name">strong</span>></span>{participantCount}<span class="hljs-tag"></<span class="hljs-name">strong</span>></span> participants.
        <span class="hljs-tag"></<span class="hljs-name">div</span>></span></span>
      );
    }
    

    Project Demo

    Telehealth Final Project Demo

    Congratulations! You have successfully integrated Stream’s chat and video function into your application.

    Conclusion

    And that’s a wrap!

    You’ve built a telehealth app with secure video, real-time chat, and user authentication – all powered by Stream’s Chat and Video SDKs.

    This foundation gives you the flexibility to expand further with features like appointment scheduling, patient history, or HIPPA-compliant file sharing.

    You can find the frontend and backend applications on GitHub. The frontend app is hosted using the Vercel hosting service, and the backend is hosted on Render.

    Check out the repository of the application.

    Happy coding! 🚀

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleDon’t Tap The Glass Tyler The Creator Shirt
    Next Article I took a walk with Meta’s new Oakley smart glasses – they beat my Ray-Bans in every way

    Related Posts

    Development

    How to Fine-Tune Large Language Models

    September 5, 2025
    Artificial Intelligence

    Scaling Up Reinforcement Learning for Traffic Smoothing: A 100-AV Highway Deployment

    September 5, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Microsoft expands Sovereign Cloud for Europe with stronger privacy tools

    Operating Systems

    CVE-2025-53569 – Trust Payments Gateway for WooCommerce CSRF Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-9736 – O2OA Cross-Site Scripting Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-8745 – Weee RICEPO App Android AndroidManifest.xml Component Export Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    Web Development

    What is Off-the-Shelf Software? Benefits, Drawbacks, and Cost

    July 31, 2025

    Off-the-shelf software, also known as Commercial Off-the-Shelf (COTS) software, refers to pre-built digital solutions that…

    CVE-2025-40652 – CoverManager Stored Cross-Site Scripting (XSS) Vulnerability

    May 26, 2025

    CVE-2025-53483 – Mediawiki SecurePoll CSRF

    July 4, 2025

    How Long Does a Cloud Migration Really Take? A Tech Expert’s Breakdown

    August 28, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.