Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

13. Logic Building — Register Controller

Lesson 13 of 23 in the free Backend Development notes on Siksha Sarovar, written by Rohit Jangra.

Why the Register Controller is Critical

The register controller is often the first real controller you write in a MERN project. It combines input validation, database operations, file uploads, and error handling all in one place. Writing it correctly from the start establishes the patterns you'll follow throughout the entire backend.

---

Complete Register Flow — Step by Step

Step 1: Extract User Details from req.body

const { username, email, fullName, password } = req.body;

Step 2: Validation — Check Not Empty, Trim, Validate Email

// Check all required fields are present and not just whitespace
if ([username, email, fullName, password].some((field) => !field?.trim())) {
  throw new ApiError(400, 'All fields are required');
}

// Validate email format with regex
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
  throw new ApiError(400, 'Invalid email format');
}

// Validate username: only lowercase letters, numbers, underscores
const usernameRegex = /^[a-z0-9_]{3,30}$/;
if (!usernameRegex.test(username.toLowerCase())) {
  throw new ApiError(400, 'Username must be 3-30 chars: lowercase letters, numbers, underscores only');
}

// Validate password strength
if (password.length < 8) {
  throw new ApiError(400, 'Password must be at least 8 characters');
}

Step 3: Check if User Already Exists

const existingUser = await User.findOne({
  $or: [{ username: username.toLowerCase() }, { email: email.toLowerCase() }],
});

if (existingUser) {
  if (existingUser.username === username.toLowerCase()) {
    throw new ApiError(409, 'Username is already taken');
  }
  throw new ApiError(409, 'An account with this email already exists');
}

Using $or checks both username and email in a single database query instead of two separate queries.

Step 4: Check for Avatar File

// req.files is populated by Multer middleware
const avatarLocalPath = req.files?.avatar?.[0]?.path;

if (!avatarLocalPath) {
  throw new ApiError(400, 'Avatar image is required');
}

// Cover image is optional
const coverImageLocalPath = req.files?.coverImage?.[0]?.path;

Step 5: Upload Avatar to Cloudinary

const avatar = await uploadOnCloudinary(avatarLocalPath);

if (!avatar?.secure_url) {
  throw new ApiError(500, 'Failed to upload avatar. Please try again.');
}

let coverImageUrl = '';
if (coverImageLocalPath) {
  const coverImage = await uploadOnCloudinary(coverImageLocalPath);
  coverImageUrl = coverImage?.secure_url || '';
}

Step 6: Create User in Database

const user = await User.create({
  fullName,
  email: email.toLowerCase().trim(),
  username: username.toLowerCase().trim(),
  password,  // Will be hashed by pre-save hook
  avatar: avatar.secure_url,
  coverImage: coverImageUrl,
});

Step 7: Fetch Created User — Exclude Sensitive Fields

const createdUser = await User.findById(user._id).select('-password -refreshToken');

if (!createdUser) {
  throw new ApiError(500, 'Something went wrong while creating the user');
}

We re-fetch instead of using the user object from User.create() because the password field is select: false — re-fetching gives us the clean document without sensitive fields.

Step 8: Return Success Response

return res
  .status(201)
  .json(new ApiResponse(201, createdUser, 'User registered successfully'));

---

Input Sanitisation

Always sanitise user input before using it:

  • trim(): Remove leading and trailing whitespace
  • toLowerCase(): Normalise email and username for consistency
  • Do not trust the client: Re-validate on the server even if you validate on the frontend
Never use user-provided input directly in MongoDB queries without sanitisation. Use Mongoose's built-in validators and avoid $where queries with user input to prevent NoSQL injection.

---

Error Handling at Each Step

StepWhat Can Go WrongError to Throw
Input extractionMissing fields, wrong types400 Bad Request
Email validationInvalid format400 Bad Request
Duplicate checkUsername or email taken409 Conflict
File checkAvatar not uploaded400 Bad Request
Cloudinary uploadNetwork error, API limit500 Internal Server Error
User.create()DB error, validation failure500 / 422
Re-fetchExtremely rare DB inconsistency500 Internal Server Error

---

Common Register Controller Mistakes

  1. Not checking req.files for undefined — always use optional chaining: req.files?.avatar?.[0]?.path
  2. Not deleting temp files on error — if Cloudinary upload fails, the temp file stays. The uploadOnCloudinary utility handles this.
  3. Not using $or for duplicate check — two separate queries are less efficient and can have race conditions
  4. Storing unhashed passwords — the pre-save hook handles hashing, but make sure it is correctly defined
  5. Returning the user object from User.create() directly — it includes the hashed password. Always re-fetch with .select('-password -refreshToken')