User Registration
Welcome to the Club
Section titled “Welcome to the Club”The Registration flow is the very first step in establishing an authenticated session. Before a user can log in, they must exist. But we can’t just throw raw input directly into the database.
Registration involves five distinct hurdles the incoming data must clear:
- Validation: Ensure the input has required fields and a valid email format.
- Uniqueness: Check the database to strictly confirm whether a user with this email already exists. (We don’t want duplicates).
- Hashing: Take the raw, plain-text password from the form payload and run it through
bcryptto generate thepasswordHash. - Insertion: Execute the MongoDB
insertcommand to actually save the newly minted user record. - Auto-Login (Optional but friendly): Once inserted, immediately provision a new session for the user so they don’t have to manually log right back in.
Professor Solo: The “Check if user already exists” step is crucial. If you
attempt an insert operation that violates a MongoDB unique constraint (like
our unique email index), MongoDB will throw a harsh Exception. We want to
catch that early and return a polite validation error to the user instead.
Data Access: Encapsulating the Logic
Section titled “Data Access: Encapsulating the Logic”Instead of throwing Mongoose logic directly into our routers, we will encapsulate the uniqueness check, the BCrypt hashing, and the actual document creation into our /data/users.js file alongside the schema.
const bcrypt = require("bcrypt");// ... (User Schema defined above) ...
class UserOps { async createUser(name, email, password) { // 1. Uniqueness Check const existingUser = await User.findOne({ email }); if (existingUser) { throw new Error("Email_In_Use"); }
// 2. Hash the raw password const saltRounds = 10; const passwordHash = await bcrypt.hash(password, saltRounds);
// 3. Insert Record const user = new User({ name, email, passwordHash }); await user.save(); return user; }}
module.exports = new UserOps();The Router View
Section titled “The Router View”When mapped over to our Express router, the logic becomes significantly cleaner. The Router simply parses the payload, calls our data module, and handles the HTTP response.
const express = require("express");const router = express.Router();const _userOps = require("../data/users");
// Render the visual EJS Formrouter.get("/register", (req, res) => { res.render("admin/register", { error: null });});
// Handle the Form POST submissionrouter.post("/register", async (req, res) => { const { name, email, password } = req.body;
// 1. Validation basics if (!name || !email || !password) return res.status(400).send("Bad Payload");
try { // 2. Execute Data Operation const user = await _userOps.createUser(name, email, password);
// 3. Auto-Login req.login(user, (err) => { if (err) throw err; res.redirect("/admin"); }); } catch (error) { if (error.message === "Email_In_Use") { return res .status(409) .render("admin/register", { error: "Email already in use." }); } return res.status(500).send("Internal Error"); }});
module.exports = adminRouter;The EJS View
Section titled “The EJS View”Finally, we need the actual HTML form for the user to interact with. Since we are using EJS layouts, we only need to write the specific content for the <main> block, making sure to explicitly display the error string passed down from our router if validation fails.
<h2>Create an Admin Account</h2>
<% if (error) { %><div class="alert alert-danger"><%= error %></div><% } %>
<form action="/admin/register" method="POST"> <label for="name">Display Name</label> <input type="text" id="name" name="name" required />
<label for="email">Email Address</label> <input type="email" id="email" name="email" required />
<label for="password">Strong Password</label> <input type="password" id="password" name="password" required />
<button type="submit">Register Account</button></form>T.A. Watts Note: A common pitfall is forgetting the await keyword before
bcrypt.hash(). The hashing operation is mathematically intense and therefore
asynchronous. Failing to await it will result in saving an unresolved
Promise object to your database instead of a string hash. That user is never
logging in.
Once a user is safely sequestered into the database, it’s time to build the machinery that lets them back through the door.
Extra Bits & Bytes
Section titled “Extra Bits & Bytes”📘 User Registration Flow Infographic (PNG)
⏭ Next: The Login Flow
Section titled “⏭ Next: The Login Flow”They exist in the database. Now let’s actually let them inside.