DEV Community

Cover image for Building a Secure Authentication API with TypeScript, Node.js, and MongoDB
FredAbod
FredAbod

Posted on

Building a Secure Authentication API with TypeScript, Node.js, and MongoDB

When I started building an authentication app, I thought it would be easy—just ask for a password, right? Fast-forward to me battling bugs and TypeScript errors, learning about bcrypt and JWTs, and questioning my life choices. But guess what? I made it work! Now, I finally built something that not only works but also won’t get hacked by a 12-year-old in their basement, and I’m here to share how I did it in simple steps. Let’s dive in!

DIVE IN

What’s this Authentication App About?

This app is like the bouncer at a party—it makes sure only the right people get in. It lets users sign up, log in, and keeps their data secure with hashed passwords and JSON Web Tokens (JWTs). Plus, it’s built with TypeScript, so everything is extra reliable.

The Routes: What Does This App Do?

  • POST /signup
    New user? No problem! This route handles user registration, validating their details against a schema to ensure everything checks out.

  • POST /login
    Checks if a user exists and if their password matches. If all is good, it hands over a shiny JWT for secure access.

  • GET /verify-email/:token
    Verifies a user’s email using a unique token, ensuring only legitimate accounts are activated.

  • POST /forgot-password
    Forgot your password? This route lets users request a password reset by validating their email and sending a secure reset link.

  • POST /reset-password/:token
    Allows users to reset their password using the token they received in their email, ensuring the process is both secure and seamless.

Now Let's Begin

Lets Begin

Step 1: Lets set up your project

run this cmds

mkdir typescript-authentication-tutorial
cd typescript-authentication-tutorial
npm init -y
Enter fullscreen mode Exit fullscreen mode

Step 2: Lets install All The Packages We'll need

npm install bcryptjs cors dotenv express helmet jsonwebtoken mongoose morgan nodemailer zod && npm install -D @types/bcryptjs @types/cors @types/express @types/helmet @types/jsonwebtoken @types/mongoose @types/morgan @types/nodemailer ts-node-dev typescript
Enter fullscreen mode Exit fullscreen mode

Here's a brief explanation of what all the Packages do

Dependencies

  • express: The web framework for building APIs.
  • bcryptjs: Used to hash and compare passwords securely.
  • cors: Middleware to handle Cross-Origin Resource Sharing (CORS).
  • dotenv: Loads environment variables from a .env file.
  • helmet: Adds security headers to your API.
  • jsonwebtoken: For creating and verifying JSON Web Tokens (JWTs).
  • mongoose: The library to work with MongoDB databases.
  • morgan: Logs HTTP requests for debugging.
  • nodemailer: Sends emails (e.g., for password resets).
  • zod: Schema validation library for input validation.

DevDependencies

  • typescript: TypeScript compiler to add static typing.
  • ts-node-dev: Runs TypeScript files directly with hot-reloading.
  • @types/express: Type definitions for Express.
  • @types/bcryptjs: Type definitions for bcryptjs.
  • @types/cors: Type definitions for CORS.
  • @types/helmet: Type definitions for Helmet.
  • @types/jsonwebtoken: Type definitions for JSON Web Tokens.
  • @types/mongoose: Type definitions for Mongoose.
  • @types/morgan: Type definitions for Morgan.
  • @types/nodemailer: Type definitions for Nodemailer.

Step 3: Configure TypeScript

Run The Command:
tsc --init

Step 4: Now The Folder Structure:

This command would create the folders and files in one go🫢🫢

mkdir -p src/{config,controllers,middleware,models,routes,services,templates,types,utils,validators} && \
touch src/config/config.ts \
src/controllers/user.controller.ts \
src/middleware/auth.ts \
src/middleware/validate.ts \
src/models/user.models.ts \
src/routes/user.routes.ts \
src/services/email.service.ts \
src/templates/resetPassword.html \
src/templates/verifyEmail.html \
src/types/user.types.ts \
src/utils/error.ts \
src/validators/user.validators.ts \
src/app.ts \
src/server.ts
Enter fullscreen mode Exit fullscreen mode

Step 5: Let's configure our tsconfig.json file and also our scripts in package.json

in tsconfig.json add

    "rootDir": "./src",  
    "outDir": "./dist",   
Enter fullscreen mode Exit fullscreen mode

in package.json change your scripts too

  "scripts": {
    "start": "node dist/server.js",
    "dev": "ts-node-dev src/server.ts",
    "build": "tsc"
  },
Enter fullscreen mode Exit fullscreen mode

Now Let's Code

Lets Goo

Lets start with our app.ts

import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import { config } from './config/config';
import userRoutes from './routes/user.routes';

const app = express();

// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.use('/api/users', userRoutes);

// Error handling middleware
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
  console.error(err.stack);
  res.status(500).json({
    status: 'error',
    message: 'Internal server error',
  });
});

export default app;
Enter fullscreen mode Exit fullscreen mode

Now server.ts

import mongoose from 'mongoose';
import { config } from './config/config';
import app from './app';

/**
 * Starts the server by connecting to MongoDB and then listening on the specified port.
 * 
 * @async
 * @function startServer
 * @throws Will throw an error if the server fails to start.
 * @returns {Promise<void>} A promise that resolves when the server is successfully started.
 */

const startServer = async () => {
  try {
    await mongoose.connect(config.mongodb.uri);
    console.log('Connected to MongoDB');

    app.listen(config.port, () => {
      console.log(`Server running on port ${config.port}`);
    });
  } catch (error) {
    console.error('Failed to start server:', error);
    process.exit(1);
  }
};

startServer();
Enter fullscreen mode Exit fullscreen mode

Now our config.ts in the config folder


// src/config/config.ts
import dotenv from 'dotenv';
import path from 'path';

// Load environment variables from .env file
dotenv.config({ path: path.join(__dirname, '../../.env') });

export const config = {
  port: process.env.PORT || 3000,
  mongodb: {
    uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/mydatabase',
  },
  jwt: {
    secret: process.env.JWT_SECRET || 'default-secret-key',
    expiresIn: process.env.JWT_EXPIRES_IN || '1h',
  },
  bcrypt: {
    saltRounds: parseInt(process.env.SALT_ROUNDS || '10', 10),
  },
  email: {
    host: process.env.EMAIL_HOST,
    port: parseInt(process.env.EMAIL_PORT || '587', 10),
    user: process.env.EMAIL_USER,
    pass: process.env.EMAIL_PASS,
  },
  frontend: {
    url: process.env.FRONTEND_URL,
  },
} as const;
Enter fullscreen mode Exit fullscreen mode

Now create a .env file

You can just use this cmd: touch .env
here is what would be in the file

# Server Configuration
PORT=your_server_port

# Database Configuration
MONGODB_URI=your_mongodb_connection_string

# JWT Configuration
JWT_SECRET=your_jwt_secret
JWT_EXPIRES_IN=your_jwt_expiry_time

# Security
SALT_ROUNDS=your_salt_rounds_value

# Email Service Configuration
EMAIL_HOST=your_email_host
EMAIL_PORT=your_email_port
EMAIL_USER=your_email_user
EMAIL_PASS=your_email_password

# Frontend Configuration
FRONTEND_URL=your_frontend_url
Enter fullscreen mode Exit fullscreen mode

Now auth.ts and validate.ts in middleware folder

auth.ts

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { config } from '../config/config';

export interface AuthRequest extends Request {
  userId?: string;
}

/**
 * Middleware to authenticate a JWT token from the request headers.
 *
 * @param req - The request object, extended to include `userId` if authentication is successful.
 * @param res - The response object.
 * @param next - The next middleware function in the stack.
 *
 * @returns A response with status 401 if no token is provided, or status 403 if the token is invalid or expired.
 *
 * @remarks
 * This middleware expects the JWT token to be provided in the `Authorization` header in the format `Bearer <token>`.
 * If the token is valid, the `userId` from the token payload is attached to the request object.
 */
export const authenticateToken = (
  req: AuthRequest,
  res: Response,
  next: NextFunction
) => {
  const authHeader = req.headers.authorization;
  const token = authHeader?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ message: 'Authentication token required' });
  }

  try {
    const decoded = jwt.verify(token, config.jwt.secret) as { userId: string };
    req.userId = decoded.userId;
    next();
  } catch (error) {
    return res.status(403).json({ message: 'Invalid or expired token' });
  }
};
Enter fullscreen mode Exit fullscreen mode

validate.ts

import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';

/**
 * Middleware to validate the request body against a given Zod schema.
 *
 * @param schema - The Zod schema to validate the request body against.
 * @returns An Express middleware function that validates the request body.
 *
 * @throws Will respond with a 400 status code and a JSON error message if validation fails.
 */
export const validateRequest = (schema: ZodSchema) => {
  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      await schema.parseAsync(req.body);
      next();
    } catch (error) {
      if (error instanceof Error) {
        res.status(400).json({
          status: 'error',
          message: 'Validation failed',
          errors: 'errors' in error ? (error as any).errors : [error.message],
        });
        return;
      }
      next(error);
    }
  };
};
Enter fullscreen mode Exit fullscreen mode

Dont Give Up

Now Let's Do user.types.ts in the types folder

/**
 * Interface representing a User.
 * 
 * @interface IUser
 * @property {string} _id - Unique identifier for the user.
 * @property {string} name - Name of the user.
 * @property {string} email - Email address of the user.
 * @property {string} password - Password of the user.
 * @property {Boolean} isVerified - Indicates if the user's email is verified.
 * @property {string} [resetPasswordToken] - Token used for resetting the password (optional).
 * @property {Date} [resetPasswordExpires] - Expiry date for the reset password token (optional).
 * @property {string} [verificationToken] - Token used for email verification (optional).
 * @property {Date} [verificationTokenExpires] - Expiry date for the verification token (optional).
 * @property {Date} createdAt - Date when the user was created.
 * @property {Date} updatedAt - Date when the user was last updated.
 */
export interface IUser {
    _id: string;
    name: string;
    email: string;
    password: string;
    isVerified: Boolean;
    resetPasswordToken?: string;
    resetPasswordExpires?: Date;
    verificationToken?: string;
    verificationTokenExpires?: Date;
    createdAt: Date;
    updatedAt: Date;
  }

  export interface IUserInput {
    name: string;
    email: string;
    password: string;
  }
Enter fullscreen mode Exit fullscreen mode

Now error.ts in the utils folder

import { Response } from 'express';

export class CustomError extends Error {
  public statusCode: number;

  constructor(message: string, statusCode: number) {
    super(message);
    this.statusCode = statusCode;
    // Set the prototype explicitly to fix instanceof checks
    Object.setPrototypeOf(this, CustomError.prototype);
  }
}

export const createCustomError = (message: string, statusCode: number): CustomError => {
  return new CustomError(message, statusCode);
};

/**
 * Handles errors in controller functions and sends appropriate HTTP responses.
 *
 * @param error - The error object that was thrown.
 * @param res - The Express response object.
 * @returns The HTTP response with the appropriate status code and error message.
 *
 * If the error is an instance of `CustomError`, it sends a response with the status code and message from the error.
 * Otherwise, it logs the error to the console and sends a 500 Internal Server Error response.
 */
export const handleControllerError = (error: unknown, res: Response): Response => {
  if (error instanceof CustomError) {
    return res.status(error.statusCode).json({
      status: 'error',
      message: error.message,
    });
  }

  console.error('Unexpected error:', error);
  return res.status(500).json({
    status: 'error',
    message: 'Internal server error',
  });
};
Enter fullscreen mode Exit fullscreen mode

Now user.validator.ts in validators folder

import { z } from 'zod';

/**
 * Schema for validating user signup data.
 * 
 * This schema ensures that the user provides:
 * - A name that is a string with a minimum length of 2 and a maximum length of 50.
 * - An email that is a valid email address.
 * - A password that is a string with a minimum length of 8 and a maximum length of 100.
 */
export const    signupSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  password: z.string().min(8).max(100),
});

export const loginSchema = z.object({
  email: z.string().email(),
  password: z.string(),
});

export const resetPasswordSchema = z.object({
  password: z.string().min(8).max(100),
});

export const forgotPasswordSchema = z.object({
  email: z.string().email(),
});
Enter fullscreen mode Exit fullscreen mode

Now lets setup Our email.service.ts in the services folder

import nodemailer from 'nodemailer';
import fs from 'fs/promises';
import path from 'path';
import { config } from '../config/config';

export class EmailService {
  // Create a transporter object using the default SMTP transport
  private static transporter = nodemailer.createTransport({
    service: 'gmail',
    auth: {
      user: config.email.user,
      pass: config.email.pass,
    },
    debug: true,
    logger: true
  });

  // Read and return the email template content from the specified file
  private static async getTemplate(templateName: string): Promise<string> {
    const templatePath = path.join(__dirname, '../templates', `${templateName}.html`);
    return await fs.readFile(templatePath, 'utf-8');
  }

  // Replace variables in the template with actual values
  private static replaceTemplateVariables(template: string, variables: Record<string, string>): string {
    return Object.entries(variables).reduce(
      (acc, [key, value]) => acc.replace(new RegExp(`{{${key}}}`, 'g'), value),
      template
    );
  }

  // Verify the SMTP connection
  static async verifyConnection(): Promise<boolean> {
    try {
      await this.transporter.verify();
      console.log('SMTP connection verified successfully');
      return true;
    } catch (error) {
      console.error('SMTP connection verification failed:', error);
      return false;
    }
  }

  // Send a verification email to the specified recipient
  static async sendVerificationEmail(
    to: string,
    name: string,
    verificationToken: string
  ): Promise<void> {
    try {
      const template = await this.getTemplate('verifyEmail');
      const verificationLink = `${config.frontend.url}/verify-email?token=${verificationToken}`;

      const html = this.replaceTemplateVariables(template, {
        name,
        verificationLink,
      });

      const mailOptions = {
        from: `"FredAbod" <${config.email.user}>`,
        to,
        subject: 'Verify Your Email',
        html,
      };

      const info = await this.transporter.sendMail(mailOptions);
      console.log('Verification email sent successfully:', info.messageId);
    } catch (error) {
      console.error('Error sending verification email:', error);
      throw new Error('Failed to send verification email');
    }
  }

  // Send a password reset email to the specified recipient
  static async sendPasswordResetEmail(
    to: string,
    name: string,
    resetToken: string
  ): Promise<void> {
    try {
      const template = await this.getTemplate('resetPassword');
      const resetLink = `${config.frontend.url}/reset-password?token=${resetToken}`;

      const html = this.replaceTemplateVariables(template, {
        name,
        resetLink,
      });

      const mailOptions = {
        from: `"FredAbod" <${config.email.user}>`,
        to,
        subject: 'Reset Your Password',
        html,
      };

      const info = await this.transporter.sendMail(mailOptions);
      console.log('Password reset email sent successfully:', info.messageId);
    } catch (error) {
      console.error('Error sending password reset email:', error);
      throw new Error('Failed to send password reset email');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Lets Setup the templates we'll need

resetPassword.html

<!DOCTYPE html>
<html>
<head>
    <style>
        .email-container {
            max-width: 600px;
            margin: 0 auto;
            font-family: Arial, sans-serif;
            padding: 20px;
        }
        .button {
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div class="email-container">
        <h2>Reset Your Password</h2>
        <p>Hello {{name}},</p>
        <p>We received a request to reset your password. Click the button below to create a new password:</p>
        <a href="{{resetLink}}" class="button">Reset Password</a>
        <p>If you didn't request this, you can safely ignore this email. Your password will remain unchanged.</p>
        <p>This link will expire in 1 hour.</p>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

verifyEmail.html

<html>
<head>
    <style>
        .email-container {
            max-width: 600px;
            margin: 0 auto;
            font-family: Arial, sans-serif;
            padding: 20px;
        }
        .button {
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 15px 32px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div class="email-container">
        <h2>Verify Your Email</h2>
        <p>Hello {{name}},</p>
        <p>Thank you for registering! Please click the button below to verify your email address:</p>
        <a href="{{verificationLink}}" class="button">Verify Email</a>
        <p>If you didn't create an account, you can safely ignore this email.</p>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now let's setup our database model

user.Models.ts

import mongoose, { Schema } from 'mongoose';
import { IUser } from '../types/user.types';

const userSchema = new Schema<IUser>(
  {
    name: { 
      type: String, 
      required: true, 
      trim: true 
    },
    email: {
      type: String,
      required: true,
      unique: true,
      trim: true,
      lowercase: true,
      index: true 
    },
    password: { 
      type: String, 
      required: true 
    },
    resetPasswordToken: String,
    resetPasswordExpires: Date,
    isVerified: {
        type: Boolean,
        default: false,
      },
      verificationToken: String,
      verificationTokenExpires: Date,
    },
  { 
    timestamps: true 
  }
);


export const User = mongoose.model<IUser>('User', userSchema);
Enter fullscreen mode Exit fullscreen mode

Now to the main logic the user.controller.ts

import { Request, Response } from "express";
import { User } from "../models/user.Models";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import { config } from "../config/config";
import { IUserInput } from "../types/user.types";
import { createCustomError } from "../utils/error";
import { EmailService } from "../services/email.service";
import crypto from "crypto";

export class UserController {
  // User signup method
  static async signup(req: Request, res: Response): Promise<void> {
    try {
      const { name, email, password }: IUserInput = req.body;

      // Check if user already exists
      const existingUser = await User.findOne({ email });
      if (existingUser) {
      res.status(400).json({
        status: 'error',
        message: 'User already exists',
      });
      return;
      }

      // Verify SMTP connection
      const isEmailServiceWorking = await EmailService.verifyConnection();
      if (!isEmailServiceWorking) {
      res.status(500).json({
        status: 'error',
        message: 'Email service is not available. Please try again later.',
      });
      return;
      }

      // Generate verification token and hash password
      const verificationToken = crypto.randomBytes(32).toString('hex');
      const hashedPassword = await bcrypt.hash(password, config.bcrypt.saltRounds);

      // Create new user
      const user = await User.create({
      name,
      email,
      password: hashedPassword,
      verificationToken,
      verificationTokenExpires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
      });

      try {
      // Send verification email
      await EmailService.sendVerificationEmail(email, name, verificationToken);

      res.status(201).json({
        status: 'success',
        message: 'Registration successful. Please check your email to verify your account.',
      });
      } catch (emailError) {
      // If email fails, mark user as requiring email verification retry
      console.error('Failed to send verification email:', emailError);
      await User.findByIdAndUpdate(user._id, {
        $set: {
        emailVerificationFailed: true
        }
      });

      res.status(201).json({
        status: 'warning',
        message: 'Account created but verification email could not be sent. Please contact support.',
        userId: user._id
      });
      }
    } catch (error) {
      console.error('Signup error:', error);
      res.status(500).json({
      status: 'error',
      message: 'Internal server error',
      });
    }
    }

  // User login method
  static async login(req: Request, res: Response): Promise<void> {
  try {
    const { email, password } = req.body;

    // Find user by email
    const user = await User.findOne({ email });
    if (!user) {
    res.status(401).json({
      status: "error",
      message: "Invalid credentials",
    });
    return;
    }

    // Check if user is verified
    if (user.isVerified != true) {
    res.status(401).json({
      status: "error",
      message: "Verify Email",
    });
    return;
    }

    // Validate password
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
    res.status(401).json({
      status: "error",
      message: "Invalid credentials",
    });
    return;
    }

    // Generate JWT token
    const token = jwt.sign({ userId: user._id }, config.jwt.secret, {
    expiresIn: config.jwt.expiresIn,
    });

    res.json({
    status: "success",
    data: {
      token,
      user: {
      id: user._id,
      name: user.name,
      email: user.email,
      },
    },
    });
  } catch (error) {
    res.status(500).json({
    status: "error",
    message: "Internal server error",
    });
  }
  }

  // Email verification method
  static async verifyEmail(req: Request, res: Response): Promise<void> {
  try {
    const { token } = req.params;

    // Find user by verification token
    const user = await User.findOne({
    verificationToken: token,
    verificationTokenExpires: { $gt: new Date() },
    });

    if (!user) {
    res.status(400).json({
      status: "error",
      message: "Invalid or expired verification token",
    });
    return;
    }

    // Mark user as verified
    user.isVerified = true;
    user.verificationToken = undefined;
    user.verificationTokenExpires = undefined;
    await user.save();

    res.json({
    status: "success",
    message: "Email verified successfully",
    });
  } catch (error) {
    res.status(500).json({
    status: "error",
    message: "Internal server error",
    });
  }
  }

  // Forgot password method
  static async forgotPassword(req: Request, res: Response): Promise<void> {
  try {
    const { email } = req.body;

    // Find user by email
    const user = await User.findOne({ email });
    if (!user) {
    res.status(404).json({
      status: 'error',
      message: 'No account found with that email',
    });
    return;
    }

    // Verify email service before proceeding
    const isEmailServiceWorking = await EmailService.verifyConnection();
    if (!isEmailServiceWorking) {
    res.status(500).json({
      status: 'error',
      message: 'Email service is not available. Please try again later.',
    });
    return;
    }

    // Generate reset token
    const resetToken = crypto.randomBytes(32).toString('hex');
    user.resetPasswordToken = resetToken;
    user.resetPasswordExpires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
    await user.save();

    try {
    // Send password reset email
    await EmailService.sendPasswordResetEmail(email, user.name, resetToken);

    res.json({
      status: 'success',
      message: 'Password reset instructions sent to your email',
    });
    } catch (emailError) {
    console.error('Failed to send password reset email:', emailError);

    // Reset the token since email failed
    user.resetPasswordToken = undefined;
    user.resetPasswordExpires = undefined;
    await user.save();

    res.status(500).json({
      status: 'error',
      message: 'Failed to send password reset email. Please try again later.',
    });
    }
  } catch (error) {
    console.error('Forgot password error:', error);
    res.status(500).json({
    status: 'error',
    message: 'Internal server error',
    });
  }
  }

  // Reset password method
  static async resetPassword(req: Request, res: Response): Promise<void> {
  try {
    const { token } = req.params;
    const { password } = req.body;

    // Find user by reset token
    const user = await User.findOne({
    resetPasswordToken: token,
    resetPasswordExpires: { $gt: new Date() },
    });

    if (!user) {
    res.status(400).json({
      status: "error",
      message: "Invalid or expired reset token",
    });
    return;
    }

    // Hash new password and save
    const hashedPassword = await bcrypt.hash(
    password,
    config.bcrypt.saltRounds
    );
    user.password = hashedPassword;
    user.resetPasswordToken = undefined;
    user.resetPasswordExpires = undefined;
    await user.save();

    res.json({
    status: "success",
    message: "Password reset successfully",
    });
  } catch (error) {
    res.status(500).json({
    status: "error",
    message: "Internal server error",
    });
  }
  }
}
Enter fullscreen mode Exit fullscreen mode

And Finally user.routes.js

import { Router } from 'express';
import { UserController } from '../controllers/user.controller';
import { validateRequest } from '../middleware/validate';
import { signupSchema, loginSchema, resetPasswordSchema, forgotPasswordSchema } from '../validators/user.validators';

/**
 * Initializes a new Router instance.
 * This router will be used to define user-related routes.
 */
const router = Router();

router.post('/signup', validateRequest(signupSchema), UserController.signup);
router.post('/login', validateRequest(loginSchema), UserController.login);
router.get('/verify-email/:token', UserController.verifyEmail);
router.post('/forgot-password', validateRequest(forgotPasswordSchema), UserController.forgotPassword);
router.post('/reset-password/:token', validateRequest(resetPasswordSchema), UserController.resetPassword);

export default router;
Enter fullscreen mode Exit fullscreen mode

And Thats's it 😊😊😊

Done

Do npm run build to compile your code and npm run dev to test...

If this article was helpful like ❤️❤️❤️ and comment feel free to drop your questions in the comment too😉

See You On The Next👌👌

Top comments (0)