4.1 – Signup a New User

What You’ll Learn

  • How to design a user model with Mongoose
  • How to validate request data using express-validator
  • How to prevent duplicate user registration
  • How to create a signup controller with clear error handling
  • How to connect route, controller, and validator cleanly

Step 1: Define the User Model (models/User.ts)

A Mongoose model is how we define the structure of our documents in MongoDB.

sh
import * as mongoose from ‘mongoose’;
import { model } from ‘mongoose’;
const userSchema = new mongoose.Schema({
email: { type: String, required: true },
password: { type: String, required: true },
username: { type: String, required: true },
created_at: { type: Date, required: true, default: new Date() },
updated_at: { type: Date, required: true, default: new Date() },
});
export default model(‘users’, userSchema);

Explanation:

  • email, password, and username are required.
  • We also store timestamps using created_at and updated_at.
  • The model name is 'users', which MongoDB will convert into the users collection.

Step 2: Create Validators (validators/UserValidator.ts)

This file will validate the user input when a signup request is sent.

sh
import { body } from ‘express-validator’;
import User from ‘../models/User’;
export class UserValidators {
static signUp() {
return [
body(’email’, ‘Email is Required’)
.isEmail()
.custom(async (email) => {
const existingUser = await User.findOne({ email });
if (existingUser) {
throw new Error(‘User already exists’);
}
return true;
}),
body(‘password’)
.isAlphanumeric()
.isLength({ min: 8, max: 20 })
.withMessage(‘Password must be 8–20 characters’),
body(‘username’, ‘Username is required’).isString(),
];
}
}

Explanation:

  • .isEmail() checks for a valid email
  • .custom() ensures no duplicate user
  • .isAlphanumeric() and .isLength() secure the password
  • .isString() ensures a string-type username

Step 3: Signup Controller (controllers/UserController.ts)

Handles the signup logic after request validation.

sh
import { validationResult } from ‘express-validator’;
import User from ‘../models/User’;
export class UserController {
static async signUp(req, res, next) {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
status_code: 400,
message: errors.array()[0].msg,
});
}
const { email, password, username } = req.body;
try {
const newUser = new User({ email, password, username });
const savedUser = await newUser.save();
res.status(201).json({
status_code: 201,
user: savedUser,
});
} catch (err) {
next(err);
}
}
}

Explanation:

  • Uses validationResult to check for errors
  • If valid, creates a new user instance
  • Saves the user to MongoDB and sends a response

Step 4: Setup the Route (routes/UserRouter.ts)

Connects the validator and controller to handle the /signup route.

sh
import { Router } from ‘express’;
import { UserController } from ‘../controllers/UserController’;
import { UserValidators } from ‘../validators/UserValidator’;
export class UserRouter {
public router: Router;
constructor() {
this.router = Router();
this.postRoutes();
}
postRoutes() {
this.router.post(‘/signup’, UserValidators.signUp(), UserController.signUp);
}
}
export default new UserRouter().router;

Explanation:

  • We apply UserValidators.signUp() before calling the controller
  • Keeps logic clean and modular

Step 5: Testing in Postman

Endpoint: POST http://localhost:5000/api/user/signup

Test Scenarios:

Test Input Expected Output
Missing email {} Email is Required
Missing password { email: "..." } Password is Required
Missing username { email, password } Username is Required
Duplicate email Same email twice User already exists
Valid input All fields 201 Created + user data

Final Output (Sample Success Response)

sh
{
“status_code”: 201,
“user”: {
“email”: “[email protected]”,
“password”: “password123”,
“username”: “testuser”,
“created_at”: “2025-06-15T11:08:29.428Z”,
“updated_at”: “2025-06-15T11:08:29.428Z”,
“_id”: “684eaa881e8f06226a5a97b8”,
“__v”: 0
}
}

 

Scroll to Top