{"id":1160,"date":"2022-04-22T10:54:59","date_gmt":"2022-04-22T10:54:59","guid":{"rendered":"https:\/\/codevoweb.com\/?p=1160"},"modified":"2024-01-25T18:29:57","modified_gmt":"2024-01-25T18:29:57","slug":"node-typescript-mongodb-jwt-authentication","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/node-typescript-mongodb-jwt-authentication\/","title":{"rendered":"Node.js + TypeScript + MongoDB: JWT Authentication"},"content":{"rendered":"\n<p>In this article, you&#8217;ll learn how to add JSON Web Token (JWT) Authentication to your Node.js app with TypeScript, MongoDB, Mongoose, Typegoose, Docker, Redis, and Zod.<\/p>\n\n\n\n<p>Node.js, TypeScript, and MongoDB Tutorial Series:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/codevoweb.com\/node-typescript-mongodb-jwt-authentication\">Node.js + TypeScript + MongoDB: JWT Authentication<\/a><\/li>\n\n\n\n<li><a href=\"\/react-node-access-refresh-tokens-authentication\">Node.js + TypeScript + MongoDB:  JWT Refresh Token<\/a><\/li>\n\n\n\n<li><a href=\"\/google-oauth-authentication-react-and-node\">Google OAuth Authentication React.js and Node.js(No Passport)<\/a><\/li>\n<\/ul>\n\n\n\n<p>You can also read:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/form-validation-react-hook-form-material-ui-react\">Form Validation with React Hook Form, Material UI, React and TypeScript<\/a><\/li>\n\n\n\n<li><a href=\"\/setup-material-ui-v5-with-react-js-and-typescript\">How to Setup Material-UI v5 with React JS and TypeScript<\/a><\/li>\n\n\n\n<li><a href=\"\/16-complicated-programming-terms-you-need-to-know\">16 Complicated Programming Terms You Need To Know<\/a><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"420\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Node.js-TypeScript-MongoDB-JWT-Authentication.webp\" alt=\"Node.js + TypeScript + MongoDB JWT Authentication\" class=\"wp-image-1249\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Node.js-TypeScript-MongoDB-JWT-Authentication.webp 800w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Node.js-TypeScript-MongoDB-JWT-Authentication-300x158.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Node.js-TypeScript-MongoDB-JWT-Authentication-768x403.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Node.js-TypeScript-MongoDB-JWT-Authentication-100x53.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Node.js-TypeScript-MongoDB-JWT-Authentication-700x368.webp 700w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Node.js-TypeScript-MongoDB-JWT-Authentication-600x315.webp 600w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/figure>\n\n\n<style>.kb-table-of-content-nav.kb-table-of-content-id1160_3e9ddd-7f .kb-table-of-content-wrap{padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}.kb-table-of-content-nav.kb-table-of-content-id1160_3e9ddd-7f .kb-table-of-contents-title-wrap{padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;}.kb-table-of-content-nav.kb-table-of-content-id1160_3e9ddd-7f .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id1160_3e9ddd-7f .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id1160_3e9ddd-7f .kb-table-of-content-wrap .kb-table-of-content-list{color:#ffffff;font-weight:regular;font-style:normal;margin-top:10px;margin-right:0px;margin-bottom:0px;margin-left:-5px;}@media all and (max-width: 1024px){.kb-table-of-content-nav.kb-table-of-content-id1160_3e9ddd-7f .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}@media all and (max-width: 767px){.kb-table-of-content-nav.kb-table-of-content-id1160_3e9ddd-7f .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>In this course, you&#8217;ll learn how to build a Node.js backend with TypeScript, implement user authentication and authorization with JWT, store data in Redis, and spawn Docker containers with docker-compose.<\/p>\n\n\n\n<p>The JWT Authentication Architecture is built with:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/nodejs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Node.js<\/a> &#8211; a JavaScript run-time scripting language<\/li>\n\n\n\n<li><a href=\"https:\/\/expressjs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Expressjs<\/a> &#8211; serves as a Node.js framework<\/li>\n\n\n\n<li><a href=\"https:\/\/typegoose.github.io\/typegoose\/\" target=\"_blank\" rel=\"noreferrer noopener\">Typegoose<\/a> &#8211; serves as a wrapper around Mongoose to allow us to write Mongoose models with TypeScript classes.<\/li>\n\n\n\n<li><a href=\"https:\/\/mongoosejs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Mongoose<\/a> &#8211; an ODM (Object Document Mapping) for accessing and mutating the database<\/li>\n\n\n\n<li><a href=\"https:\/\/www.npmjs.com\/package\/bcryptjs\" target=\"_blank\" rel=\"noreferrer noopener\">Bcryptjs<\/a> &#8211; for hashing the passwords<\/li>\n\n\n\n<li><a href=\"https:\/\/www.npmjs.com\/package\/jsonwebtoken\" target=\"_blank\" rel=\"noreferrer noopener\">JsonWebToken<\/a> &#8211; generating JWTs<\/li>\n\n\n\n<li><a href=\"https:\/\/www.npmjs.com\/package\/redis\" target=\"_blank\" rel=\"noreferrer noopener\">Redis<\/a> &#8211; as caching storage for storing the user&#8217;s session<\/li>\n\n\n\n<li><a href=\"https:\/\/www.mongodb.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">MongoDB<\/a> &#8211; as NoSQL database<\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/colinhacks\/zod\" target=\"_blank\" rel=\"noreferrer noopener\">Zod<\/a> &#8211; for validating user inputs<\/li>\n\n\n\n<li><a href=\"https:\/\/www.npmjs.com\/package\/cors\" target=\"_blank\" rel=\"noreferrer noopener\">cors<\/a> &#8211; To allow Cross-Origin Resource Sharing between the backend and frontend<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">What the course will cover<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>How to use TypeScript in Node.js and Express<\/li>\n\n\n\n<li>How to model data with Mongoose, Typegoose, and TypeScript<\/li>\n\n\n\n<li>How to spawn docker containers with docker-compose<\/li>\n\n\n\n<li>JWT Authentication with Private and Public keys<\/li>\n\n\n\n<li>How to store data in Redis<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>To follow along with this tutorial, make sure you have the following:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Software<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/nodejs.org\/en\/download\/\" target=\"_blank\" rel=\"noreferrer noopener\">Node.js<\/a> &#8211; for scripting the backend logic<\/li>\n\n\n\n<li><a href=\"https:\/\/www.docker.com\/products\/docker-desktop\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker<\/a> &#8211; allows us to package applications into containers<\/li>\n\n\n\n<li><a href=\"https:\/\/www.mongodb.com\/try\/download\/compass\" target=\"_blank\" rel=\"noreferrer noopener\">MongoDB compass<\/a> (optional) &#8211; A GUI for querying, mutating, analyzing, and aggregating MongoDB data.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">VS Code Extensions<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ms-azuretools.vscode-docker\" target=\"_blank\" rel=\"noreferrer noopener\">Docker<\/a> (optional) &#8211; Manage docker containers directly in VS Code<\/li>\n\n\n\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=mikestead.dotenv\" target=\"_blank\" rel=\"noreferrer noopener\">DotENV<\/a> &#8211; Get syntax highlighting in the environment variables file<\/li>\n\n\n\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=mkloubert.vscode-http-client\" target=\"_blank\" rel=\"noreferrer noopener\">HTTP Client<\/a> &#8211; For making HTTP requests to the server. You can also use <a href=\"https:\/\/www.postman.com\/downloads\/\" target=\"_blank\" rel=\"noreferrer noopener\">Postman<\/a> or <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=humao.rest-client\" target=\"_blank\" rel=\"noreferrer noopener\">REST Client<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=cweijan.vscode-mysql-client2\" target=\"_blank\" rel=\"noreferrer noopener\">MySQL<\/a> &#8211; Database client for VS Code. Allows us to see what&#8217;s in the Redis database.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Assumed Knowledge<\/h2>\n\n\n\n<p>The course assumes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You have basic knowledge of Node.js<\/li>\n\n\n\n<li>You have basic knowledge of TypeScript and JavaScript<\/li>\n\n\n\n<li>Have basic knowledge of NoSQL databases<\/li>\n\n\n\n<li>Have basic knowledge of Mongoose<\/li>\n\n\n\n<li>You have basic knowledge of Docker<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Run the Frontend and Backend Apps<\/h2>\n\n\n\n<p>To successfully run both the backend and frontend projects on your machine, follow these step-by-step instructions:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Begin by downloading or cloning the project from its GitHub repository: <a href=\"https:\/\/github.com\/wpcodevo\/JWT_Authentication_React\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/wpcodevo\/JWT_Authentication_React<\/a>. Open the source code in your preferred text editor.<\/li>\n\n\n\n<li>In the integrated terminal of your IDE or text editor, execute the command <code>docker-compose up -d<\/code> to launch the MongoDB and Redis Docker containers.<\/li>\n\n\n\n<li>Navigate to the backend directory using the command <code>cd .\/backend<\/code>. Run <code>yarn install<\/code> to install all required dependencies.<\/li>\n\n\n\n<li>Open the <code>backend\/src\/app.ts<\/code> file and uncomment the Nodemailer code.<br><img loading=\"lazy\" decoding=\"async\" width=\"1000\" height=\"547\" class=\"wp-image-12400\" style=\"width: 1000px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/nodemailer-code-of-the-backend-api.webp\" alt=\"nodemailer code of the backend api\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/nodemailer-code-of-the-backend-api.webp 1207w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/nodemailer-code-of-the-backend-api-300x164.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/nodemailer-code-of-the-backend-api-1024x560.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/nodemailer-code-of-the-backend-api-768x420.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/nodemailer-code-of-the-backend-api-100x55.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/nodemailer-code-of-the-backend-api-700x383.webp 700w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><br>Then, run the command <code>yarn start<\/code> to initiate the Node.js development server. You should see the Nodemailer SMTP credentials printed in the terminal. Copy the values of the &#8216;<strong>user<\/strong>&#8216; and &#8216;<strong>pass<\/strong>&#8216; fields and add them to their respective variables in the <code>backend\/.env<\/code> file. Afterward, comment out the Nodemailer code in the <code>backend\/src\/app.ts<\/code> file, and save the file to restart the Node.js development server.<\/li>\n\n\n\n<li>In another terminal, move to the frontend directory from the root level using the command <code>cd .\/frontend<\/code>. Run <code>yarn install<\/code> to install all necessary dependencies. Once the installation is complete, execute <code>yarn dev<\/code> to start the Vite development server.<\/li>\n\n\n\n<li>Open the application in your browser by visiting <code>http:\/\/localhost:3000\/<\/code> and explore the app&#8217;s features. During the registration and password reset process, a Nodemailer link will be printed in the terminal of the backend server that you can click to open the mailbox.<br><img loading=\"lazy\" decoding=\"async\" width=\"1000\" height=\"872\" class=\"wp-image-12401\" style=\"width: 1000px;\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/Nodemailer-mailbox-web-app-in-the-browser.webp\" alt=\"Nodemailer mailbox web app in the browser\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/Nodemailer-mailbox-web-app-in-the-browser.webp 1170w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/Nodemailer-mailbox-web-app-in-the-browser-300x262.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/Nodemailer-mailbox-web-app-in-the-browser-1024x893.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/Nodemailer-mailbox-web-app-in-the-browser-768x670.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/Nodemailer-mailbox-web-app-in-the-browser-100x87.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/Nodemailer-mailbox-web-app-in-the-browser-516x450.webp 516w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><br><strong>Note<\/strong>: Do not visit the app using <code>http:\/\/127.0.0.1:3000\/<\/code> to avoid getting CORS errors.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Setup Development Environment (Optional)<\/h2>\n\n\n\n<p>To follow along with this course, you need to have Node.js, Docker, and MongoDB Compass installed on your machine.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Download and Install Node.js<\/h3>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"808\" height=\"575\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/official-homepage-of-nodejs.png\" alt=\"official homepage of nodejs\" class=\"wp-image-1206\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/official-homepage-of-nodejs.png 808w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/official-homepage-of-nodejs-300x213.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/official-homepage-of-nodejs-768x547.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/official-homepage-of-nodejs-100x71.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/official-homepage-of-nodejs-632x450.png 632w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/official-homepage-of-nodejs-600x427.png 600w\" sizes=\"auto, (max-width: 808px) 100vw, 808px\" \/><\/figure>\n\n\n\n<p>To download Node.js, visit the <a href=\"https:\/\/nodejs.org\/en\/\" target=\"_blank\" rel=\"noreferrer noopener\">official download page<\/a> of Node.js and download the current or LTS (Long Term Support) version of Node.js.<\/p>\n\n\n\n<p>After the installation is complete, run the installer wizard and accept the default options.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Download and Install Docker<\/h3>\n\n\n\n<p>To download Docker, visit the <a href=\"https:\/\/www.docker.com\/products\/docker-desktop\/\" target=\"_blank\" rel=\"noreferrer noopener\">official download<\/a> page of Docker and download the right version for your operating system.<\/p>\n\n\n\n<p>The Docker installer will automatically install Docker-compose.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Download and Install MongoDB Compass<\/h3>\n\n\n\n<p>To download and install MongoDB Compass, visit the <a href=\"https:\/\/www.mongodb.com\/try\/download\/compass\" target=\"_blank\" rel=\"noreferrer noopener\">official MongoDB Compass<\/a> download page and download the right version for your operating system. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Node.js, Redis, MongoDB, Typegoose, Docker: JWT Authentication example<\/h2>\n\n\n\n<p>With this JWT Authentication Rest API, the user will be able to do the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Signup for a new account with a Name, Email, Password, and Password Confirm fields.<\/li>\n\n\n\n<li>Login with the Email and Password credentials.<\/li>\n\n\n\n<li>Get the profile information only if he is logged in.<\/li>\n\n\n\n<li>Admin will be able to get all the users in the database.<\/li>\n<\/ul>\n\n\n\n<p>These are the API endpoints we need for this Rest API<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>RESOURCE<\/th><th>HTTP METHOD<\/th><th>ROUTE<\/th><th>DESCRIPTION<\/th><\/tr><\/thead><tbody><tr><td>users<\/td><td>GET<\/td><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/users<\/mark><\/td><td>returns all the users and their information<\/td><\/tr><tr><td>users<\/td><td>GET<\/td><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/users\/me<\/mark><\/td><td>return the logged-in user&#8217;s information<\/td><\/tr><tr><td>auth<\/td><td>POST<\/td><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/auth\/register<\/mark><\/td><td>Create a new user<\/td><\/tr><tr><td>auth<\/td><td>POST<\/td><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/auth\/login<\/mark><\/td><td>Logs the user in<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">JWT Authentication Flow with Redis, MongoDB, and Node.js<\/h2>\n\n\n\n<p>This is the JWT Authentication flow we will follow in this tutorial. The user visits our app in the browser and provides his username and password to log into our application.<\/p>\n\n\n\n<p>The frontend app will then make a request to the backend with the user&#8217;s credentials. The backend will then authenticate the user and send back some cookies if the credentials are valid.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"576\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-with-NodeJs.webp\" alt=\"JWT Authentication with NodeJs\" class=\"wp-image-1207\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-with-NodeJs.webp 800w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-with-NodeJs-300x216.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-with-NodeJs-768x553.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-with-NodeJs-100x72.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-with-NodeJs-625x450.webp 625w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-with-NodeJs-600x432.webp 600w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/figure>\n\n\n\n<p>The diagram below illustrates the user login flow in our application<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"520\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-User-Login.webp\" alt=\"JWT Authentication User Login\" class=\"wp-image-1208\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-User-Login.webp 800w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-User-Login-300x195.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-User-Login-768x499.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-User-Login-100x65.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-User-Login-692x450.webp 692w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/JWT-Authentication-User-Login-600x390.webp 600w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/figure>\n\n\n\n<p>The diagram below shows the user registration flow in the JWT Authentication flow.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"520\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/User-Registration-with-JWT-Authentication.webp\" alt=\"User Registration with JWT Authentication\" class=\"wp-image-1210\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/User-Registration-with-JWT-Authentication.webp 800w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/User-Registration-with-JWT-Authentication-300x195.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/User-Registration-with-JWT-Authentication-768x499.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/User-Registration-with-JWT-Authentication-100x65.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/User-Registration-with-JWT-Authentication-692x450.webp 692w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/User-Registration-with-JWT-Authentication-600x390.webp 600w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Project Structure<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>jwt_authentication_authorization_node\/\n\u251c\u2500\u2500 .vscode\/\n\u2502 \u2514\u2500\u2500 extensions.json\n\u251c\u2500\u2500 config\/\n\u2502 \u251c\u2500\u2500 custom-environment-variables.ts\n\u2502 \u2514\u2500\u2500 default.ts\n\u251c\u2500\u2500 http\/\n\u2502 \u251c\u2500\u2500 getUsers.http-request\n\u2502 \u251c\u2500\u2500 login.http-request\n\u2502 \u251c\u2500\u2500 me.http-request\n\u2502 \u2514\u2500\u2500 register.http-request\n\u251c\u2500\u2500 src\/\n\u2502 \u251c\u2500\u2500 controllers\/\n\u2502 \u2502 \u251c\u2500\u2500 auth.controller.ts\n\u2502 \u2502 \u2514\u2500\u2500 user.controller.ts\n\u2502 \u251c\u2500\u2500 middleware\/\n\u2502 \u2502 \u251c\u2500\u2500 deserializeUser.ts\n\u2502 \u2502 \u251c\u2500\u2500 requireUser.ts\n\u2502 \u2502 \u251c\u2500\u2500 restrictTo.ts\n\u2502 \u2502 \u2514\u2500\u2500 validate.ts\n\u2502 \u251c\u2500\u2500 models\/\n\u2502 \u2502 \u2514\u2500\u2500 user.model.ts\n\u2502 \u251c\u2500\u2500 routes\/\n\u2502 \u2502 \u251c\u2500\u2500 auth.route.ts\n\u2502 \u2502 \u2514\u2500\u2500 user.route.ts\n\u2502 \u251c\u2500\u2500 schema\/\n\u2502 \u2502 \u2514\u2500\u2500 user.schema.ts\n\u2502 \u251c\u2500\u2500 services\/\n\u2502 \u2502 \u2514\u2500\u2500 user.service.ts\n\u2502 \u251c\u2500\u2500 utils\/\n\u2502 \u2502 \u251c\u2500\u2500 appError.ts\n\u2502 \u2502 \u251c\u2500\u2500 connectDB.ts\n\u2502 \u2502 \u251c\u2500\u2500 connectRedis.ts\n\u2502 \u2502 \u2514\u2500\u2500 jwt.ts\n\u2502 \u2514\u2500\u2500 app.ts\n\u251c\u2500\u2500 .env\n\u251c\u2500\u2500 .gitignore\n\u251c\u2500\u2500 docker-compose.yml\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 tsconfig.json\n\u2514\u2500\u2500 yarn.lock<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Project Setup<\/h2>\n\n\n\n<p>Create a new project folder with the following command<\/p>\n\n\n\n<pre class=\"language-powershell\"><code>\nmkdir jwt_auth\ncd jwt_auth\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Initialize a Node.js Project with TypeScript<\/h3>\n\n\n\n<p>The first thing we always do before coding a Node.js project that will require external libraries is to initialize a new project with the following command.<\/p>\n\n\n\n<pre class=\"language-shell\"><code>\n# with yarn\nyarn init\n# with npm \nnpm init\n<\/code>\n<\/pre>\n\n\n\n<p>You will be prompted to provide some answers. If you don&#8217;t want to answer questions then use the <code><strong>-y<\/strong> <\/code>flag.<\/p>\n\n\n\n<p>You should see logs like this in your terminal if you answered the prompted questions.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ yarn init\nyarn init v1.22.18\nquestion name (test): Jwt_Auth\nquestion version (1.0.0):\nquestion description: Jwt Authentication with Node.js, Typegoose, mongoD\nB, Mongoose, Docker, Redis, and JsonWebToken\nquestion entry point (index.js): app.ts\nquestion repository url:\nquestion author: Codevo\nquestion license (MIT):\nquestion private:\nsuccess Saved package.json\nDone in 21.33s.<\/code><\/pre>\n\n\n\n<p>Run the command below to install TypeScript as a dev dependency. This will allow us compile the TypeScript code into pure JavaScript using the TypeScript compiler. <\/p>\n\n\n\n<pre class=\"language-shell\"><code>\n# with yarn\nyarn add -D typescript\n# with npm \nnpm init -y\nnpm install -D typescript\n<\/code>\n<\/pre>\n\n\n\n<p>Run the following command to initialize a TypeScript project. A tsconfig.json file will be created in your root directory.<\/p>\n\n\n\n<pre class=\"language-shell\"><code>\nnpx tsc --init\n<\/code>\n<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">TypeScript tsconfig.json file configurations<\/h4>\n\n\n\n<p>Add the following configuration options to your <strong>tsconfig.json<\/strong> file to allow us use decorators and more in our code. <\/p>\n\n\n\n<pre class=\"line-numbers language-json\"><code>\n{\n  \"compilerOptions\": {\n    \"target\": \"es2016\",\n    \"experimentalDecorators\": true,\n    \"emitDecoratorMetadata\": true,\n    \"module\": \"commonjs\",\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"strictPropertyInitialization\": false,\n    \"skipLibCheck\": true\n  }\n}\n\n<\/code>\n<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Important configurations in the tsconfig.json<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>experimentalDecorators: true<\/li>\n\n\n\n<li>emitDecoratorMetadata: true<\/li>\n\n\n\n<li>strictPropertyInitialization: false<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Install the Required Libraries<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Install the Dependencies<\/h4>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\n# npm\nnpm install @typegoose\/typegoose bcryptjs config cookie-parser dotenv express jsonwebtoken lodash mongoose redis ts-node-dev zod cors\n\n# yarn\nyarn add @typegoose\/typegoose bcryptjs config cookie-parser dotenv express jsonwebtoken lodash mongoose redis ts-node-dev zod cors\n\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>dotenv<\/code><\/strong> &#8211; loads environment variables from a <code>.env<\/code> file into <code>process.env<\/code> <\/li>\n\n\n\n<li><strong><code>@typegoose\/typegoose<\/code><\/strong> &#8211; writing Mongoose models with TypeScript class<\/li>\n\n\n\n<li><strong><code>bcryptjs<\/code><\/strong> &#8211; to hash the password data<\/li>\n\n\n\n<li><strong><code>config<\/code><\/strong> &#8211; allow us to provide TypeScript types for the environment variables we import from the <code>.env<\/code> file<\/li>\n\n\n\n<li><strong><code>cookie-parser<\/code><\/strong> &#8211; to parse the cookies in the request headers and attach them to <code>req.cookies<\/code> <\/li>\n\n\n\n<li><code><strong>jsonwebtoken<\/strong><\/code> &#8211; to sign and verify JWTs<\/li>\n\n\n\n<li><code>lodash<\/code> &#8211; contains utilities for simplifying common programming tasks.<\/li>\n\n\n\n<li><code>ts-node-dev<\/code> &#8211; allow us run the server. An alternative solution is <code>nodemon<\/code> and <code>ts-node<\/code>.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">Install the devDependencies<\/h4>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\n# npm\nnpm install -D morgan typescript\n# yarn\nyarn add -D morgan typescript\n<\/code>\n<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Install the Type Definition files<\/h4>\n\n\n\n<p>These type definition files are needed for TypeScript to function properly.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\n# npm\nnpm install -D @types\/bcryptjs @types\/config @types\/cookie-parser @types\/express @types\/jsonwebtoken @types\/lodash @types\/morgan @types\/node @types\/cors\n# yarn\nyarn add -D @types\/bcryptjs @types\/config @types\/cookie-parser @types\/express @types\/jsonwebtoken @types\/lodash @types\/morgan @types\/node @types\/cors\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Initialize and Start the Express Server<\/h3>\n\n\n\n<p>Create an src folder in the root directory and within the src folder create a file named <code>app.ts<\/code> .<\/p>\n\n\n\n<p>Copy and paste the boilerplate code for the express server.<\/p>\n\n\n\n<p><strong>src\/app.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nrequire('dotenv').config();\nimport express from 'express';\nimport config from 'config';\n\nconst app = express();\n\nconst port = config.get&lt;number&gt;('port');\napp.listen(port, () =&gt; {\n  console.log(`Server started on port: ${port}`);\n});\n<\/code>\n<\/pre>\n\n\n\n<p>In the code snippets above, I imported the <a href=\"https:\/\/www.npmjs.com\/package\/dotenv\" target=\"_blank\" rel=\"noreferrer noopener\">dotenv<\/a> package and configured it at the top level of the <code>app.ts<\/code> file.<\/p>\n\n\n\n<p>Then I created an instance of the express class and called the listen method with the port we want to run the server on and a callback function.<\/p>\n\n\n\n<p>Since we are making use of environment variables it makes sense to create a <code>.env<\/code> file in the root directory.<\/p>\n\n\n\n<p>Now create a <code>.env<\/code> file in the root directory and add the following code.<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-env\"><code>\nNODE_ENV=development\nMONGODB_USERNAME=codevoweb\nMONGODB_PASSWORD=password123\nMONGODB_DATABASE_NAME=jwtAuth\n\nACCESS_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBTlFLQStSV2ZQZFdHR25iYS9WRVo1TUs5cG1nMUlQay9paEE5dXF2Ny8rNVlzRjNUVURoCnFHZXN1bGJhdFFGdkNPaHVmSlNJQmFWT3RjbVZrTWZoWmRrQ0F3RUFBUUpBYkVlTkF6NnpaQzhBR3BhbGc4TmgKelBJdFNmaWFiWnd6dWVTcTh0L1RoRmQrUGhqN2IxTmphdjBMTjNGamhycjlzV3B2UjBBNW13OFpoSUFUNzZMUgpzUUloQU95Zmdhdy9BSTVoeGs3NmtWaVRRV0JNdjdBeERwdi9oSG1aUFdxclpyL1ZBaUVBNVdjalpmK0NaYlhTCnlpV3dUbEVENGVZQ3BSNk16Qk8wbFVhbExKdVRFL1VDSUhWTWZSUE9CNUNObDZqL1BaNFRJWTJEZm1MeGJyU1cKYmkxNWNhQzNaekFoQWlBNmUrVG1hQkdTWkp4c3ROY1I0RTJoRmNhdTJlOERTRExOcThrSWFsRkEwUUloQUlwUApUODFlWlNzYmVrNTlidGJPZ3J3bTJBdzJqUVk4TitJa3FMSTNySWFFCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t\nACCESS_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTlFLQStSV2ZQZFdHR25iYS9WRVo1TUs5cG1nMUlQawovaWhBOXVxdjcvKzVZc0YzVFVEaHFHZXN1bGJhdFFGdkNPaHVmSlNJQmFWT3RjbVZrTWZoWmRrQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==\n\n<\/code>\n<\/pre>\n\n\n\n<p>Within the .env file, I added public and private access token keys which are base64 encoded.<\/p>\n\n\n\n<p>I also provided the MongoDB database credentials we will need for the mongo docker container.<\/p>\n\n\n\n<p>I will later show how you can generate those private and public keys. Also, make sure you change the database credentials.<\/p>\n\n\n\n<p>Next, create a config folder in the root directory and create two files named <code>default.ts<\/code> and <code>custom-environment-variables.ts<\/code> in the config folder.<\/p>\n\n\n\n<p>Open the default.ts file and add the following code<\/p>\n\n\n\n<p><strong>config\/default.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nexport default {\n  port: 8000,\n  accessTokenExpiresIn: 15,\norigin: 'http:\/\/localhost:3000',\n};\n<\/code>\n<\/pre>\n\n\n\n<p>Next, add the following code to the <code>custom-environment-variables.ts<\/code> file.<\/p>\n\n\n\n<p><strong>config\/custom-environment-variables.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nexport default {\n  dbName: 'MONGODB_USERNAME',\n  dbPass: 'MONGODB_PASSWORD',\n  accessTokenPrivateKey: 'ACCESS_TOKEN_PRIVATE_KEY',\n  accessTokenPublicKey: 'ACCESS_TOKEN_PUBLIC_KEY',\n};\n<\/code>\n<\/pre>\n\n\n\n<p>The <strong>custom-environment-variables.ts<\/strong> file will allow us to import the environment variables we defined in the <strong>.env<\/strong> file.<\/p>\n\n\n\n<p>Now add the start script to the package.json file<\/p>\n\n\n\n<pre class=\"line-numbers language-json\"><code>\n\"scripts\": {\n    \"start\": \"ts-node-dev --respawn --transpile-only src\/app.ts\"\n  }\n<\/code>\n<\/pre>\n\n\n\n<p>If you followed all the instructions above, your final package.json file should look somehow like this:<\/p>\n\n\n\n<p><strong>package.json<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-json\"><code>\n{\n  \"name\": \"JWT_Auth\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"start\": \"ts-node-dev --respawn --transpile-only src\/app.ts\"\n  },\n  \"dependencies\": {\n    \"@typegoose\/typegoose\": \"^9.8.0\",\n    \"bcryptjs\": \"^2.4.3\",\n    \"config\": \"^3.3.7\",\n    \"cookie-parser\": \"^1.4.6\",\n    \"dotenv\": \"^16.0.0\",\n    \"express\": \"^4.17.3\",\n    \"jsonwebtoken\": \"^8.5.1\",\n    \"lodash\": \"^4.17.21\",\n    \"mongoose\": \"^6.3.0\",\n    \"redis\": \"^4.0.6\",\n    \"ts-node-dev\": \"^1.1.8\",\n    \"zod\": \"^3.14.4\"\n  },\n  \"devDependencies\": {\n    \"@types\/bcryptjs\": \"^2.4.2\",\n    \"@types\/config\": \"^0.0.41\",\n    \"@types\/cookie-parser\": \"^1.4.2\",\n    \"@types\/express\": \"^4.17.13\",\n    \"@types\/jsonwebtoken\": \"^8.5.8\",\n    \"@types\/lodash\": \"^4.14.182\",\n    \"@types\/morgan\": \"^1.9.3\",\n    \"@types\/node\": \"^17.0.25\",\n    \"morgan\": \"^1.10.0\",\n    \"typescript\": \"^4.6.3\"\n  }\n}\n\n<\/code>\n<\/pre>\n\n\n\n<p>Finally, open your terminal and run the start script to start the Express server.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn start\n<\/code>\n<\/pre>\n\n\n\n<p>This will start the Express server on port <strong>8000<\/strong> or the port you specified.<\/p>\n\n\n\n<p>Click on this <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">http:\/\/localhost:8000\/healthChecker<\/mark> link and you should see this in a new tab.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"761\" height=\"442\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/express-app-health-checker.png\" alt=\"express app health checker\" class=\"wp-image-1213\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/express-app-health-checker.png 761w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/express-app-health-checker-300x174.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/express-app-health-checker-100x58.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/express-app-health-checker-700x407.png 700w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/express-app-health-checker-600x348.png 600w\" sizes=\"auto, (max-width: 761px) 100vw, 761px\" \/><\/figure>\n\n\n\n<p>Once you see the Welcome message then it means you did everything correctly.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Setting up Redis and MongoDB with Docker Compose<\/h3>\n\n\n\n<p>I assume you already have docker installed on your computer. In the root directory create a <code>docker-compose.yml<\/code> file and add the code below.<\/p>\n\n\n\n<p><strong>docker-compose.yml<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-yml\"><code>\nversion: '3.8'\nservices:\n  mongo:\n    image: mongo:latest\n    container_name: mongo\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: ${MONGODB_USERNAME}\n      MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_PASSWORD}\n      MONGO_INITDB_DATABASE: ${MONGODB_DATABASE_NAME}\n    env_file:\n      - .\/.env\n    volumes:\n      - mongo:\/data\/db\n    ports:\n      - '6000:27017'\n  redis:\n    image: redis:latest\n    container_name: redis\n    ports:\n      - '6379:6379'\n    volumes:\n      - redis:\/data\nvolumes:\n  mongo:\n  redis:\n\n<\/code>\n<\/pre>\n\n\n\n<p>With the mongo container, I exposed port 6000 so that we can log into the mongo container on port 6000 in MongoDB Compass.<\/p>\n\n\n\n<p>Also, I exposed port 6379 on the Redis container to allow us to connect to the Redis container with the <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=cweijan.vscode-mysql-client2\" target=\"_blank\" rel=\"noreferrer noopener\">MySQL VS Code client extension<\/a>.<\/p>\n\n\n\n<p>Now, open your terminal and run the command below to spawn the Mongo and Redis containers.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\n# start the docker containers\ndocker-compose up -d\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Connecting to the MongoDB Docker Container with Mongoose<\/h3>\n\n\n\n<p>Since we have our MongoDB database up and running, we need to connect our Express app to it with Mongoose.<\/p>\n\n\n\n<p>In the src folder, create a <strong>utils<\/strong> folder, and within this <code>utils<\/code> folder create a <code>connectDB.ts<\/code> file and paste this code into it.<\/p>\n\n\n\n<p><strong>src\/utils\/connectDB.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport mongoose from 'mongoose';\nimport config from 'config';\n\nconst dbUrl = `mongodb:\/\/${config.get('dbName')}:${config.get(\n  'dbPass'\n)}@localhost:6000\/jwtAuth?authSource=admin`;\n\nconst connectDB = async () =&gt; {\n  try {\n    await mongoose.connect(dbUrl);\n    console.log('Database connected...');\n  } catch (error: any) {\n    console.log(error.message);\n    setTimeout(connectDB, 5000);\n  }\n};\n\nexport default connectDB;\n\n<\/code>\n<\/pre>\n\n\n\n<p>What we just did above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>imported mongoose and the config library<\/li>\n\n\n\n<li>constructed the database connection string with the properties we defined in the <code>custom-environment-variables.ts<\/code> file.<\/li>\n\n\n\n<li>I called the mongoose <code>connect<\/code> function and passed the database connection string as an argument.<\/li>\n\n\n\n<li>Lastly, I used setTimeout to call the <code>connectDB<\/code> function after every 5s when there is a connection error.<\/li>\n<\/ul>\n\n\n\n<p>The database connection URL has the following structure:<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nconst dbUrl = `mongodb:\/\/username:password@host:port\/database?authSource=admin`\n<\/code>\n<\/pre>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>NAME<\/th><th>PLACEHOLDER<\/th><th>DESCRIPTION<\/th><\/tr><\/thead><tbody><tr><td>Host<\/td><td>host<\/td><td>The domain of the database server, <strong>eg: localhost<\/strong><\/td><\/tr><tr><td>Port<\/td><td>port<\/td><td>The port on which the database server is running on, <strong>eg: 27017<\/strong><\/td><\/tr><tr><td>User<\/td><td>username<\/td><td>The  database username<\/td><\/tr><tr><td>Password<\/td><td>password<\/td><td>The password of the database user<\/td><\/tr><tr><td>Database<\/td><td>database<\/td><td>The name of the database<\/td><\/tr><tr><td>Options<\/td><td>authSource<\/td><td>The database to use when authenticating with <code>user<\/code> and <code>pass<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Connecting to Redis Docker Container<\/h3>\n\n\n\n<p>Next, let&#8217;s connect our express app to the Redis container.<\/p>\n\n\n\n<p>Inside the utils folder, create a new <code>connectRedis.ts<\/code> file then copy and paste the code snippets below into it.<\/p>\n\n\n\n<p><strong>src\/utils\/connectRedis.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { createClient } from 'redis';\n\nconst redisUrl = `redis:\/\/localhost:6379`;\nconst redisClient = createClient({\n  url: redisUrl,\n});\n\nconst connectRedis = async () =&gt; {\n  try {\n    await redisClient.connect();\n    console.log('Redis client connected...');\n  } catch (err: any) {\n    console.log(err.message);\n    setTimeout(connectRedis, 5000);\n  }\n};\n\nconnectRedis();\n\nredisClient.on('error', (err) =&gt; console.log(err));\n\nexport default redisClient;\n\n<\/code>\n<\/pre>\n\n\n\n<p>Here is a breakdown of what I did in the <code>connectRedis.ts<\/code> file:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>I imported the <code>createClient<\/code> function from the <code>redis<\/code> library<\/li>\n\n\n\n<li>I created the Redis connection URL and assigned it to redisUrl<\/li>\n\n\n\n<li>I called the <code>createClient<\/code> function and passed an object with the connection URL. I assigned the object returned by the <code>createClient<\/code> function to the redisClient variable.<\/li>\n\n\n\n<li>I then created a <code>connectRedis<\/code> function and called the connect method on the <code>redisClient<\/code> object.<\/li>\n\n\n\n<li>Lastly, I used setTimeout to call the <code>connectRedis<\/code> function after every 5s when the connection fails.<\/li>\n<\/ul>\n\n\n\n<p>Now, open <code>app.ts<\/code> and import the <code>connectDB<\/code> function we defined in the<code>connectDB.ts<\/code> file then call it below the <code>console.log()<\/code> in the callback we passed to the <code>listen<\/code> function.<\/p>\n\n\n\n<p><strong>src\/app.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nrequire('dotenv').config();\nimport express from 'express';\nimport config from 'config';\nimport connectDB from '.\/utils\/connectDB';\n\nconst app = express();\n\nconst port = config.get&lt;number&gt;('port');\napp.listen(port, () =&gt; {\n  console.log(`Server started on port: ${port}`);\n  \/\/ ? call the connectDB function here\n  connectDB();\n});\n<\/code>\n<\/pre>\n\n\n\n<p>You will see the DB connection message in the terminal assuming your server is still running.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Database Schema with Typegoose<\/h2>\n\n\n\n<p>Now create a models folder in the src folder and within the models&#8217; folder create a <code>user.model.ts<\/code> file. <\/p>\n\n\n\n<p>Typegoose makes heavy use of decorators to define a Mongoose model.<\/p>\n\n\n\n<p><strong>src\/models\/user.model.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport {\n  DocumentType,\n  getModelForClass,\n  index,\n  modelOptions,\n  pre,\n  prop,\n} from '@typegoose\/typegoose';\nimport bcrypt from 'bcryptjs';\n\n@index({ email: 1 })\n@pre&lt;User&gt;('save', async function () {\n  \/\/ Hash password if the password is new or was updated\n  if (!this.isModified('password')) return;\n\n  \/\/ Hash password with costFactor of 12\n  this.password = await bcrypt.hash(this.password, 12);\n})\n@modelOptions({\n  schemaOptions: {\n    \/\/ Add createdAt and updatedAt fields\n    timestamps: true,\n  },\n})\n\n\/\/ Export the User class to be used as TypeScript type\nexport class User {\n  @prop()\n  name: string;\n\n  @prop({ unique: true, required: true })\n  email: string;\n\n  @prop({ required: true, minlength: 8, maxLength: 32, select: false })\n  password: string;\n\n  @prop({ default: 'user' })\n  role: string;\n\n  \/\/ Instance method to check if passwords match\n  async comparePasswords(hashedPassword: string, candidatePassword: string) {\n    return await bcrypt.compare(candidatePassword, hashedPassword);\n  }\n}\n\n\/\/ Create the user model from the User class\nconst userModel = getModelForClass(User);\nexport default userModel;\n\n<\/code>\n<\/pre>\n\n\n\n<p>Breakdown of what I did above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>I created a User class and added all the properties our model requires with the Typegoose decorators and exported the class.<\/li>\n\n\n\n<li>I used the utility function <code>getModelForClass<\/code> to create a Mongoose model from the User class we defined above.<\/li>\n\n\n\n<li>I used a pre-save hook to hash the password only if the password is new or was modified.<\/li>\n\n\n\n<li>I then added the email field as an index.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">How to Generate Private and Public keys for JWT Authentication<\/h2>\n\n\n\n<p>Generating the private and public keys yourself can be challenging so am going to use <a href=\"http:\/\/travistidwell.com\/jsencrypt\/demo\/\" target=\"_blank\" rel=\"noreferrer noopener\">this website<\/a> to easily generate them.<\/p>\n\n\n\n<p>On the website, you can use either <strong>2048 bits<\/strong> or <strong>4096 bits<\/strong> as the key size but am going to use <strong>2048 bits<\/strong>. After selecting the key size, click on the <strong>&#8220;Generate New Key&#8221;<\/strong> button to generate the keys.<\/p>\n\n\n\n<p>Now let&#8217;s <a href=\"https:\/\/www.base64encode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">visit this website<\/a> to encode both keys in <strong>base64<\/strong>. Copy each of the keys and paste it into the &#8220;Encode to Base64 format&#8221; Textarea then click on the &#8220;Encode&#8221; button.<\/p>\n\n\n\n<p>After each encoding copy the encoded key and paste it into the .env file respectively.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Define Middleware to Sign and Verify JWTs<\/h2>\n\n\n\n<p>Next, create a middleware folder in the src folder and create a file named <code>jwt.ts<\/code> file in the middleware folder.<\/p>\n\n\n\n<p>Copy and paste the code below into the <code>jwt.ts<\/code> file.<\/p>\n\n\n\n<p><strong>src\/utils\/jwt.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport jwt, { SignOptions } from 'jsonwebtoken';\nimport config from 'config';\n\nexport const signJwt = (payload: Object, options: SignOptions = {}) =&gt; {\n  const privateKey = Buffer.from(\n    config.get&lt;string&gt;('accessTokenPrivateKey'),\n    'base64'\n  ).toString('ascii');\n  return jwt.sign(payload, privateKey, {\n    ...(options &amp;&amp; options),\n    algorithm: 'RS256',\n  });\n};\n\nexport const verifyJwt = &lt;T&gt;(token: string): T | null =&gt; {\n  try {\n    const publicKey = Buffer.from(\n      config.get&lt;string&gt;('accessTokenPublicKey'),\n      'base64'\n    ).toString('ascii');\n    return jwt.verify(token, publicKey) as T;\n  } catch (error) {\n    return null;\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<p>Let me explain what I did in the <code>jwt.ts<\/code> file:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>I exported a <code>signJwt<\/code> and <code>verifyJwt<\/code> functions<\/li>\n\n\n\n<li>The <code>signJwt<\/code> function is responsible for signing a new JsonWebToken. The <code>signJwt<\/code> function takes two parameters, a payload object and a SignOptions object.<\/li>\n\n\n\n<li>In the <code>signJwt<\/code> function, I converted the encoded private key we stored in the <code>.env<\/code> file to an <strong>ASCII<\/strong> string.<\/li>\n\n\n\n<li>Next, I made the <code>verifyJwt<\/code> function a generic function so that it can return the generic type or null.<\/li>\n\n\n\n<li>When the <code>verifyJwt<\/code> function turns null then it means the token is invalid or has expired<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Define a Custom Error Handler in Express<\/h2>\n\n\n\n<p>Next, let&#8217;s create a custom Express Error Handler by extending the Error class.<\/p>\n\n\n\n<p><strong>src\/utils\/appError.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nexport default class AppError extends Error {\n  status: string;\n  isOperational: boolean;\n\n  constructor(public message: string, public statusCode: number = 500) {\n    super(message);\n    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';\n    this.isOperational = true;\n\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n\n<\/code>\n<\/pre>\n\n\n\n<p>Whenever we want to send an error to the user we&#8217;ll call the next function and pass an instance of the <strong>AppError<\/strong> class with the message and the status code.<\/p>\n\n\n\n<p>In Express when you call the next function with an argument, Express assumes it&#8217;s an error so it will skip all the middleware functions and directly send the error to the error handler in the middleware pipe.<\/p>\n\n\n\n<p>In our JWT Authentication app, Express will immediately send the error we passed to the next function to the middleware below.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.use((err: any, req: Request, res: Response, next: NextFunction) =&amp;gt; {\n  err.status = err.status || 'error';\n  err.statusCode = err.statusCode || 500;\n\n  res.status(err.statusCode).json({\n    status: err.status,\n    message: err.message,\n  });\n});<\/code><\/pre>\n\n\n\n<p>The error controller will then perform some logic before sending the actual message and status code to the user who made the request.<\/p>\n\n\n\n<p>Error handling in Express can be very challenging but I simplified the error handling process in this article because I noticed the article was getting too long.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Define the Zod Validation Schema<\/h2>\n\n\n\n<p>With every backend application, it&#8217;s always good not to trust the user&#8217;s input. You should always validate the user&#8217;s input in your backend application.<\/p>\n\n\n\n<p>Mongoose itself comes with schema validation but since we are using TypeScript, we can add Zod schema validation.<\/p>\n\n\n\n<p>Below are the schema validation rules for both the login and register functions.<\/p>\n\n\n\n<p><strong>src\/schemas\/user.schema.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { object, string, TypeOf } from 'zod';\n\nexport const createUserSchema = object({\n  body: object({\n    name: string({ required_error: 'Name is required' }),\n    email: string({ required_error: 'Email is required' }).email(\n      'Invalid email'\n    ),\n    password: string({ required_error: 'Password is required' })\n      .min(8, 'Password must be more than 8 characters')\n      .max(32, 'Password must be less than 32 characters'),\n    passwordConfirm: string({ required_error: 'Please confirm your password' }),\n  }).refine((data) =&gt; data.password === data.passwordConfirm, {\n    path: ['passwordConfirm'],\n    message: 'Passwords do not match',\n  }),\n});\n\nexport const loginUserSchema = object({\n  body: object({\n    email: string({ required_error: 'Email is required' }).email(\n      'Invalid email or password'\n    ),\n    password: string({ required_error: 'Password is required' }).min(\n      8,\n      'Invalid email or password'\n    ),\n  }),\n});\n\nexport type CreateUserInput = TypeOf&lt;typeof createuserschema=\"\"&gt;['body'];\nexport type LoginUserInput = TypeOf&lt;typeof loginuserschema=\"\"&gt;['body'];\n<\/code>\n<\/pre>\n\n\n\n<p>I also used the <strong>TypeOf<\/strong> function from Zod to infer the TypeScript types of our schemas and exported them from the file.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create a Middleware to Validate the User Inputs<\/h2>\n\n\n\n<p>This function is responsible for validating the user input based on the schema passed to it as an argument.<\/p>\n\n\n\n<p><strong>src\/middleware\/validate.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { NextFunction, Request, Response } from 'express';\nimport { AnyZodObject, ZodError } from 'zod';\n\nexport const validate =\n  (schema: AnyZodObject) =&gt;\n  (req: Request, res: Response, next: NextFunction) =&gt; {\n    try {\n      schema.parse({\n        params: req.params,\n        query: req.query,\n        body: req.body,\n      });\n\n      next();\n    } catch (err: any) {\n      if (err instanceof ZodError) {\n        return res.status(400).json({\n          status: 'fail',\n          error: err.errors,\n        });\n      }\n      next(err);\n    }\n  };\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create a Service to Communicate with the Database<\/h2>\n\n\n\n<p>It&#8217;s recommended to always have services that communicate with the database for a couple of reasons.<\/p>\n\n\n\n<p>Don&#8217;t let your controllers access and mutate the database directly if you want to scale and easily test your Express app.<\/p>\n\n\n\n<p>Below are the functions that communicate with the database. I also added the logic to sign new JWT tokens.<\/p>\n\n\n\n<p><strong>src\/services\/user.service.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { omit, get } from 'lodash';\nimport { FilterQuery, QueryOptions } from 'mongoose';\nimport config from 'config';\nimport userModel, { User } from '..\/models\/user.model';\nimport { excludedFields } from '..\/controllers\/auth.controller';\nimport { signJwt } from '..\/utils\/jwt';\nimport redisClient from '..\/utils\/connectRedis';\nimport { DocumentType } from '@typegoose\/typegoose';\n\n\/\/ CreateUser service\nexport const createUser = async (input: Partial&lt;User&gt;) =&gt; {\n  const user = await userModel.create(input);\n  return omit(user.toJSON(), excludedFields);\n};\n\n\/\/ Find User by Id\nexport const findUserById = async (id: string) =&gt; {\n  const user = await userModel.findById(id).lean();\n  return omit(user, excludedFields);\n};\n\n\/\/ Find All users\nexport const findAllUsers = async () =&gt; {\n  return await userModel.find();\n};\n\n\/\/ Find one user by any fields\nexport const findUser = async (\n  query: FilterQuery&lt;User&gt;,\n  options: QueryOptions = {}\n) =&gt; {\n  return await userModel.findOne(query, {}, options).select('+password');\n};\n\n\/\/ Sign Token\nexport const signToken = async (user: DocumentType&lt;User&gt;) =&gt; {\n  \/\/ Sign the access token\n  const access_token = signJwt(\n    { sub: user._id },\n    {\n      expiresIn: `${config.get&lt;number&gt;('accessTokenExpiresIn')}m`,\n    }\n  );\n\n  \/\/ Create a Session\n  redisClient.set(user._id, JSON.stringify(user), {\n    EX: 60 * 60,\n  });\n\n  \/\/ Return access token\n  return { access_token };\n};\n\n<\/code>\n<\/pre>\n\n\n\n<p>Important things to note in the above code:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>In the <code>signToken<\/code> function, I first generated the access token and specified expiration time in minutes.<\/li>\n\n\n\n<li>I then called the set method on the redisClient object and used the user id as the key and the user&#8217;s info as the value to be stored in Redis. I also gave the data an expiration time.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Create the Authentication Controller<\/h2>\n\n\n\n<p>The authentication controller is responsible for anything regarding user authentication. Here are some of the jobs the controller can perform:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Registering new user<\/li>\n\n\n\n<li>Logging the user into his account<\/li>\n\n\n\n<li>Sending a password reset email to a user who forgets his email or password<\/li>\n\n\n\n<li>Reset the user&#8217;s password<\/li>\n\n\n\n<li>Updating the currently logged in user&#8217;s password<\/li>\n\n\n\n<li>Authentication with Google OAuth<\/li>\n\n\n\n<li>Authentication with GitHub OAuth<\/li>\n<\/ul>\n\n\n\n<p><strong>src\/controllers\/auth.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport config from 'config';\nimport { CookieOptions, NextFunction, Request, Response } from 'express';\nimport { CreateUserInput, LoginUserInput } from '..\/schema\/user.schema';\nimport { createUser, findUser, signToken } from '..\/services\/user.service';\nimport AppError from '..\/utils\/appError';\n\n\/\/ Exclude this fields from the response\nexport const excludedFields = ['password'];\n\n\/\/ Cookie options\nconst accessTokenCookieOptions: CookieOptions = {\n  expires: new Date(\n    Date.now() + config.get&lt;number&gt;('accessTokenExpiresIn') * 60 * 1000\n  ),\n  maxAge: config.get&lt;number&gt;('accessTokenExpiresIn') * 60 * 1000,\n  httpOnly: true,\n  sameSite: 'lax',\n};\n\n\/\/ Only set secure to true in production\nif (process.env.NODE_ENV === 'production')\n  accessTokenCookieOptions.secure = true;\n\nexport const registerHandler = async (\n  req: Request&lt;{}, {}, CreateUserInput&gt;,\n  res: Response,\n  next: NextFunction\n) =&gt; {\n  try {\n    const user = await createUser({\n      email: req.body.email,\n      name: req.body.name,\n      password: req.body.password,\n    });\n\n    res.status(201).json({\n      status: 'success',\n      data: {\n        user,\n      },\n    });\n  } catch (err: any) {\n    if (err.code === 11000) {\n      return res.status(409).json({\n        status: 'fail',\n        message: 'Email already exist',\n      });\n    }\n    next(err);\n  }\n};\n\nexport const loginHandler = async (\n  req: Request&lt;{}, {}, LoginUserInput&gt;,\n  res: Response,\n  next: NextFunction\n) =&gt; {\n  try {\n    \/\/ Get the user from the collection\n    const user = await findUser({ email: req.body.email });\n\n    \/\/ Check if user exist and password is correct\n    if (\n      !user ||\n      !(await user.comparePasswords(user.password, req.body.password))\n    ) {\n      return next(new AppError('Invalid email or password', 401));\n    }\n\n    \/\/ Create an Access Token\n    const { accessToken } = await signToken(user);\n\n    \/\/ Send Access Token in Cookie\n    res.cookie('accessToken', accessToken, accessTokenCookieOptions);\n    res.cookie('logged_in', true, {\n      ...accessTokenCookieOptions,\n      httpOnly: false,\n    });\n\n    \/\/ Send Access Token\n    res.status(200).json({\n      status: 'success',\n      accessToken,\n    });\n  } catch (err: any) {\n    next(err);\n  }\n};\n\n<\/code>\n<\/pre>\n\n\n\n<p>Here is a breakdown of what I did in the <strong>auth.controller.ts<\/strong> file:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We have two functions, a <code>registerHandler<\/code> and <code>loginHandler<\/code> .<\/li>\n\n\n\n<li>When the user provides his email, name and password to register for an account, the <code>registerHandler<\/code> will be called then the <code>registerHandler<\/code> will also call the <code>createUser<\/code> service with the required user credentials.<\/li>\n\n\n\n<li>The <code>createUser<\/code> service will then communicate with the user model to create the new user.<\/li>\n\n\n\n<li>In the catch block of the <code>registerHandler<\/code> I checked if the error has a code of <strong>11000<\/strong> which is a MongoDB error code of a duplicate unique field. When the error code is <strong>11000<\/strong> then it means the user already exists so we send the appropriate error message and status code.<\/li>\n\n\n\n<li>Next, in the <code>loginHandler<\/code> I checked if the user with that email exists in our MongoDB database.<\/li>\n\n\n\n<li>If the user exists then we check if the password is the same as the encrypted one in the database.<\/li>\n\n\n\n<li>Then we create a new JWT access token and send it to the user as a response and a cookie.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Create the User Controller to Test Authorization<\/h2>\n\n\n\n<p>In the user controller there are two functions:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The <code>getMeHandler<\/code> returns the currently logged in user&#8217;s profile info.<\/li>\n\n\n\n<li>The <code>getAllUsersHandler<\/code> function is only for the Admin to get all users.<\/li>\n<\/ol>\n\n\n\n<p><strong>src\/controllers\/user.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { NextFunction, Request, Response } from 'express';\nimport { findAllUsers } from '..\/services\/user.service';\n\nexport const getMeHandler = (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) =&gt; {\n  try {\n    const user = res.locals.user;\n    res.status(200).json({\n      status: 'success',\n      data: {\n        user,\n      },\n    });\n  } catch (err: any) {\n    next(err);\n  }\n};\n\nexport const getAllUsersHandler = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) =&gt; {\n  try {\n    const users = await findAllUsers();\n    res.status(200).json({\n      status: 'success',\n      result: users.length,\n      data: {\n        users,\n      },\n    });\n  } catch (err: any) {\n    next(err);\n  }\n};\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Define a function to deserialize the User<\/h2>\n\n\n\n<p>This middleware is responsible for getting the JWT Authorization bearer token and cookie from the headers and cookie object respectively.<\/p>\n\n\n\n<p>It then validates the token, checks if the user has a valid session, check if the user still exists and adds the user to <code>res.locals<\/code> if there wasn&#8217;t any error.<\/p>\n\n\n\n<p><strong>src\/middleware\/deserializeUser.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { NextFunction, Request, Response } from 'express';\nimport { findUserById } from '..\/services\/user.service';\nimport AppError from '..\/utils\/appError';\nimport redisClient from '..\/utils\/connectRedis';\nimport { verifyJwt } from '..\/utils\/jwt';\n\nexport const deserializeUser = async (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) =&gt; {\n  try {\n    \/\/ Get the token\n    let access_token;\n    if (\n      req.headers.authorization &amp;&amp;\n      req.headers.authorization.startsWith('Bearer')\n    ) {\n      access_token = req.headers.authorization.split(' ')[1];\n    } else if (req.cookies.access_token) {\n      access_token = req.cookies.access_token;\n    }\n\n    if (!access_token) {\n      return next(new AppError('You are not logged in', 401));\n    }\n\n    \/\/ Validate Access Token\n    const decoded = verifyJwt&lt;{ sub: string }&gt;(access_token);\n\n    if (!decoded) {\n      return next(new AppError(`Invalid token or user doesn't exist`, 401));\n    }\n\n    \/\/ Check if user has a valid session\n    const session = await redisClient.get(decoded.sub);\n\n    if (!session) {\n      return next(new AppError(`User session has expired`, 401));\n    }\n\n    \/\/ Check if user still exist\n    const user = await findUserById(JSON.parse(session)._id);\n\n    if (!user) {\n      return next(new AppError(`User with that token no longer exist`, 401));\n    }\n\n    \/\/ This is really important (Helps us know if the user is logged in from other controllers)\n    \/\/ You can do: (req.user or res.locals.user)\n    res.locals.user = user;\n\n    next();\n  } catch (err: any) {\n    next(err);\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Define a function to check if the user is logged in<\/h2>\n\n\n\n<p>This middleware will be called after the <strong>deserializeUser<\/strong> middleware to check if the user exists on <strong>res.locals<\/strong>.<\/p>\n\n\n\n<p><strong>src\/middleware\/requireUser.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { NextFunction, Request, Response } from 'express';\nimport AppError from '..\/utils\/appError';\n\nexport const requireUser = (\n  req: Request,\n  res: Response,\n  next: NextFunction\n) =&gt; {\n  try {\n    const user = res.locals.user;\n    if (!user) {\n      return next(new AppError(`Invalid token or session has expired`, 401));\n    }\n\n    next();\n  } catch (err: any) {\n    next(err);\n  }\n};\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Define a Middleware to Restrict Unauthorized Access<\/h2>\n\n\n\n<p>This middleware checks if the user role exists in the <code>allowedRoles<\/code> array. If the role is in the array then it means the user is allowed to perform that action else it will throw an error.<\/p>\n\n\n\n<p><strong>src\/middleware\/restrictTo.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { NextFunction, Request, Response } from 'express';\nimport AppError from '..\/utils\/appError';\n\nexport const restrictTo =\n  (...allowedRoles: string[]) =&gt;\n  (req: Request, res: Response, next: NextFunction) =&gt; {\n    const user = res.locals.user;\n    if (!allowedRoles.includes(user.role)) {\n      return next(\n        new AppError('You are not allowed to perform this action', 403)\n      );\n    }\n\n    next();\n  };\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create the Authentication Routes<\/h2>\n\n\n\n<p>Now, create two routes folders named <code>user.route.ts<\/code> and <code>auth.route.ts<\/code>  in the src folder.<\/p>\n\n\n\n<p>A route in Express is considered as a mini-app. Whenever a request matches the route in the middleware stack, Express will delegate the request to the route handler of that route.<\/p>\n\n\n\n<p><strong>src\/routes\/user.route.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport express from 'express';\nimport {\n  getAllUsersHandler,\n  getMeHandler,\n} from '..\/controllers\/user.controller';\nimport { deserializeUser } from '..\/middleware\/deserializeUser';\nimport { requireUser } from '..\/middleware\/requireUser';\nimport { restrictTo } from '..\/middleware\/restrictTo';\n\nconst router = express.Router();\nrouter.use(deserializeUser, requireUser);\n\n\/\/ Admin Get Users route\nrouter.get('\/', restrictTo('admin'), getAllUsersHandler);\n\n\/\/ Get my info route\nrouter.get('\/me', getMeHandler);\n\nexport default router;\n\n<\/code>\n<\/pre>\n\n\n\n<p>The <code>user.route.ts<\/code> contains the routes to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Get all users (only by Admin)<\/li>\n\n\n\n<li>Get the currently logged-in credentials<\/li>\n<\/ul>\n\n\n\n<p><strong>src\/routes\/auth.route.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport express from 'express';\nimport { loginHandler, registerHandler } from '..\/controllers\/auth.controller';\nimport { validate } from '..\/middleware\/validate';\nimport { createUserSchema, loginUserSchema } from '..\/schema\/user.schema';\n\nconst router = express.Router();\n\n\/\/ Register user route\nrouter.post('\/register', validate(createUserSchema), registerHandler);\n\n\/\/ Login user route\nrouter.post('\/login', validate(loginUserSchema), loginHandler);\n\nexport default router;\n<\/code>\n<\/pre>\n\n\n\n<p>The <code>auth.route.ts<\/code> contains the routes to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><span style=\"color: initial;\">Login a user<\/span><\/li>\n\n\n\n<li>Register a user<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Update the app.ts file to use the route<\/h2>\n\n\n\n<p>Next, update the <strong>app.ts<\/strong> to use the following middleware:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Body Parser middleware to parse the request body and attach it to <code>req.body<\/code> <\/li>\n\n\n\n<li>Morgan to log the HTTP request logs in the terminal<\/li>\n\n\n\n<li>Cors for Cross-Origin Resource Sharing<\/li>\n\n\n\n<li>Cookie Parser to parse the cookie and attach it to <code>req.cookies<\/code> <\/li>\n\n\n\n<li>User router<\/li>\n<\/ul>\n\n\n\n<p><strong>src\/app.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nrequire('dotenv').config();\nimport express, { NextFunction, Request, Response } from 'express';\nimport morgan from 'morgan';\nimport config from 'config';\nimport cors from 'cors';\nimport cookieParser from 'cookie-parser';\nimport connectDB from '.\/utils\/connectDB';\nimport userRouter from '.\/routes\/user.route';\nimport authRouter from '.\/routes\/auth.route';\n\nconst app = express();\n\n\/\/ Middleware\n\n\/\/ 1. Body Parser\napp.use(express.json({ limit: '10kb' }));\n\n\/\/ 2. Cookie Parser\napp.use(cookieParser());\n\n\/\/ 3. Logger\nif (process.env.NODE_ENV === 'development') app.use(morgan('dev'));\n\n\/\/ 4. Cors\napp.use(\n  cors({\n    origin: config.get&lt;string&gt;('origin'),\n    credentials: true,\n  })\n);\n\n\/\/ 5. Routes\napp.use('\/api\/users', userRouter);\napp.use('\/api\/auth', authRouter);\n\n\/\/ Testing\napp.get('\/healthChecker', (req: Request, res: Response, next: NextFunction) =&gt; {\n  res.status(200).json({\n    status: 'success',\n    message: 'Welcome to CodevoWeb????',\n  });\n});\n\n\/\/ UnKnown Routes\napp.all('*', (req: Request, res: Response, next: NextFunction) =&gt; {\n  const err = new Error(`Route ${req.originalUrl} not found`) as any;\n  err.statusCode = 404;\n  next(err);\n});\n\n\/\/ Global Error Handler\napp.use((err: any, req: Request, res: Response, next: NextFunction) =&gt; {\n  err.status = err.status || 'error';\n  err.statusCode = err.statusCode || 500;\n\n  res.status(err.statusCode).json({\n    status: err.status,\n    message: err.message,\n  });\n});\n\nconst port = config.get&lt;number&gt;('port');\napp.listen(port, () =&gt; {\n  console.log(`Server started on port: ${port}`);\n  \/\/ ? call the connectDB function here\n  connectDB();\n});\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Testing the JWT Authentication Rest API<\/h2>\n\n\n\n<p>Run this command to spawn the MongoDB and Redis Docker containers:<\/p>\n\n\n\n<pre class=\"language-shell\"><code>\ndocker-compose up -d\n<\/code>\n<\/pre>\n\n\n\n<p>You should see the following logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ docker-compose up -d\nCreating network \"jwt_auth_default\" with the default driver\nCreating redis ... done\nCreating mongo ... done<\/code><\/pre>\n\n\n\n<p>Run <code>yarn start<\/code> in the terminal and you should see the following logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ yarn start\nyarn run v1.22.18\n$ ts-node-dev --respawn --transpile-only src\/app.ts\n&#91;INFO] 08:49:09 ts-node-dev ver. 1.1.8 (using ts-node ver. 9.1.1, typescript ver. 4.6.3)\nServer started on port: 8000\nRedis client connected...\nDatabase connected...<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Register users<\/h3>\n\n\n\n<p>Let&#8217;s register some users. You can use Postman but am going to use the <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=mkloubert.vscode-http-client\" target=\"_blank\" rel=\"noreferrer noopener\">HTTP client VS Code extension<\/a>.<\/p>\n\n\n\n<p>Register some users with <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">http:\/\/localhost:8000\/api\/auth\/register<\/mark> route<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1000\" height=\"974\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-register-new-user.png\" alt=\"jwt authentication register new user\" class=\"wp-image-1428\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-register-new-user.png 1000w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-register-new-user-300x292.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-register-new-user-768x748.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-register-new-user-100x97.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-register-new-user-462x450.png 462w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-register-new-user-600x584.png 600w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Here is the response you&#8217;ll get after registering a new user<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"933\" height=\"974\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-registering-a-user.webp\" alt=\"jwt authentication response after registering a user\" class=\"wp-image-1420\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-registering-a-user.webp 933w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-registering-a-user-287x300.webp 287w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-registering-a-user-768x802.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-registering-a-user-96x100.webp 96w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-registering-a-user-431x450.webp 431w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-registering-a-user-600x626.webp 600w\" sizes=\"auto, (max-width: 933px) 100vw, 933px\" \/><\/figure>\n\n\n\n<p>Open MongoDB Compass then click on the users collection and you should see the registered users.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1001\" height=\"823\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/registered-users-in-MongoDB-database.png\" alt=\"registered users in MongoDB database\" class=\"wp-image-1243\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/registered-users-in-MongoDB-database.png 1001w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/registered-users-in-MongoDB-database-300x247.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/registered-users-in-MongoDB-database-768x631.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/registered-users-in-MongoDB-database-100x82.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/registered-users-in-MongoDB-database-547x450.png 547w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/registered-users-in-MongoDB-database-600x493.png 600w\" sizes=\"auto, (max-width: 1001px) 100vw, 1001px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Login user<\/h3>\n\n\n\n<p>Let&#8217;s login with the login credentials of any of the users you created<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"934\" height=\"976\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-login-the-registered-user.png\" alt=\"jwt authentication register new user\" class=\"wp-image-1429\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-login-the-registered-user.png 934w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-login-the-registered-user-287x300.png 287w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-login-the-registered-user-768x803.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-login-the-registered-user-96x100.png 96w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-login-the-registered-user-431x450.png 431w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-login-the-registered-user-600x627.png 600w\" sizes=\"auto, (max-width: 934px) 100vw, 934px\" \/><\/figure>\n\n\n\n<p>You should get a response with an access token. We sent a cookie with the response but this extension doesn&#8217;t show cookies. If you are using Postman then you will see the cookies in the cookie tab.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"930\" height=\"972\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-logging-in.webp\" alt=\"jwt authentication response after logging in\" class=\"wp-image-1421\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-logging-in.webp 930w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-logging-in-287x300.webp 287w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-logging-in-768x803.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-logging-in-96x100.webp 96w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-logging-in-431x450.webp 431w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-logging-in-600x627.webp 600w\" sizes=\"auto, (max-width: 930px) 100vw, 930px\" \/><\/figure>\n\n\n\n<p>Now, let&#8217;s connect to the Redis Docker container to see the logged-in users&#8217; credentials. We are storing the credentials in Redis to serve as a form of session.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"447\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Connect-To-Redis-On-Vs-Code.gif\" alt=\"Connect To Redis On Vs Code\" class=\"wp-image-1244\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Get Currently Logged in User&#8217;s Credentials<\/h3>\n\n\n\n<p>After logging in, copy the access token and add the authorization header with the token attached to the bearer string value.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"932\" height=\"967\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-get-logged-in-users-credentials.webp\" alt=\"\" class=\"wp-image-1425\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-get-logged-in-users-credentials.webp 932w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-get-logged-in-users-credentials-289x300.webp 289w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-get-logged-in-users-credentials-768x797.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-get-logged-in-users-credentials-96x100.webp 96w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-get-logged-in-users-credentials-434x450.webp 434w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-get-logged-in-users-credentials-600x623.webp 600w\" sizes=\"auto, (max-width: 932px) 100vw, 932px\" \/><\/figure>\n\n\n\n<p>You should get the users&#8217; credentials as a response if the access token is valid<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"929\" height=\"971\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-getting-logged-in-users-credentials.webp\" alt=\"jwt authentication response after getting logged in user's credentials\" class=\"wp-image-1422\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-getting-logged-in-users-credentials.webp 929w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-getting-logged-in-users-credentials-287x300.webp 287w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-getting-logged-in-users-credentials-768x803.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-getting-logged-in-users-credentials-96x100.webp 96w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-getting-logged-in-users-credentials-431x450.webp 431w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-response-after-getting-logged-in-users-credentials-600x627.webp 600w\" sizes=\"auto, (max-width: 929px) 100vw, 929px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Admin Get All Users<\/h3>\n\n\n\n<p>Get back to MongoDB Compass and change one of the users&#8217; role to admin and login with that email.<\/p>\n\n\n\n<p>Copy and paste the access token and make a request to <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">http:\/\/localhost:8000\/users<\/mark> route in order to get all the users in the database.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"930\" height=\"969\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-admin-get-all-users.webp\" alt=\"\" class=\"wp-image-1426\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-admin-get-all-users.webp 930w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-admin-get-all-users-288x300.webp 288w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-admin-get-all-users-768x800.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-admin-get-all-users-96x100.webp 96w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-admin-get-all-users-432x450.webp 432w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/05\/jwt-authentication-admin-get-all-users-600x625.webp 600w\" sizes=\"auto, (max-width: 930px) 100vw, 930px\" \/><\/figure>\n\n\n\n<p>You should get all the users if that email is the admin&#8217;s email.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1010\" height=\"974\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Admin-get-all-users.png\" alt=\"Admin get all users\" class=\"wp-image-1235\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Admin-get-all-users.png 1010w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Admin-get-all-users-300x289.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Admin-get-all-users-768x741.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Admin-get-all-users-100x96.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Admin-get-all-users-467x450.png 467w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/04\/Admin-get-all-users-600x579.png 600w\" sizes=\"auto, (max-width: 1010px) 100vw, 1010px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>In this article, we looked at how you can add JWT Authentication to your app with Node.js, Express, MongoDB, Zod, TypeScript, Docker-compose, Redis, and Mongoose.<\/p>\n\n\n\n<p>Checkout <a href=\"https:\/\/github.com\/wpcodevo\/jwt_authentication_authorization_node\/tree\/Access_Token\" target=\"_blank\" rel=\"noreferrer noopener\">source code on GitHub<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, you&#8217;ll learn how to add JSON Web Token (JWT) Authentication to your Node.js app with TypeScript, MongoDB, Mongoose, Typegoose, Docker, Redis, and&#8230;<\/p>\n","protected":false},"author":1,"featured_media":1249,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[47],"tags":[42],"class_list":["post-1160","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-nodejs","tag-nodejs-api"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/1160","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/comments?post=1160"}],"version-history":[{"count":2,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/1160\/revisions"}],"predecessor-version":[{"id":12426,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/1160\/revisions\/12426"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/1249"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=1160"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=1160"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=1160"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}