This application is a simple Express + MongoDB Airbnb-style clone that renders EJS templates and stores listings and reviews in MongoDB. The guide below captures the basics for bootstrapping and then walks through a recommended authentication stack for the project.
- Install dependencies once:
npm install. - Make sure MongoDB is running locally (default connection string lives in
app.js). - Launch the dev server:
node app.js(defaults to port8080).
Authentication is not wired in yet, but the project already includes express-session, so the pieces below reuse it to add username/password login. The snippets assume CommonJS modules to match the rest of the repo.
// models/user.js
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
passwordHash: { type: String, required: true },
});
userSchema.statics.register = async function ({ username, email, password }) {
const passwordHash = await bcrypt.hash(password, 12);
return this.create({ username, email, passwordHash });
};
userSchema.methods.validatePassword = function (password) {
return bcrypt.compare(password, this.passwordHash);
};
module.exports = mongoose.model("User", userSchema);Add flash-style helpers once after the session middleware in app.js:
const flash = require("connect-flash");
app.use(flash());
app.use((req, res, next) => {
res.locals.currentUserId = req.session.userId;
res.locals.success = req.flash("success");
res.locals.error = req.flash("error");
next();
});If you plan to deploy or restart the server often, swap the in-memory session store for MongoDB: npm install connect-mongo and pass a store option into session(sessionOptions).
Create routes/auth.js for register/login/logout flows:
const express = require("express");
const router = express.Router();
const User = require("../models/user");
router.get("/register", (req, res) => res.render("auth/register"));
router.post("/register", async (req, res, next) => {
try {
const user = await User.register(req.body);
req.session.userId = user._id;
req.flash("success", "Welcome!");
res.redirect("/listings");
} catch (err) {
next(err);
}
});
router.get("/login", (req, res) => res.render("auth/login"));
router.post("/login", async (req, res, next) => {
try {
const user = await User.findOne({ username: req.body.username });
if (!user || !(await user.validatePassword(req.body.password))) {
req.flash("error", "Invalid credentials");
return res.redirect("/login");
}
req.session.userId = user._id;
req.flash("success", "Logged in");
res.redirect("/listings");
} catch (err) {
next(err);
}
});
router.post("/logout", (req, res) => {
req.session.userId = null;
req.flash("success", "Logged out");
res.redirect("/listings");
});
module.exports = router;Mount it in app.js with app.use("/", require("./routes/auth"));.
Drop a reusable guard (for example in utils/auth.js):
module.exports.isLoggedIn = (req, res, next) => {
if (!req.session.userId) {
req.flash("error", "You must be signed in first.");
return res.redirect("/login");
}
next();
};Use the guard anywhere privileged actions happen, e.g. wrap the /listings/new, POST /listings, /listings/:id/edit, update, and delete handlers.
Add an owner field to the listing schema:
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},When creating a listing, set listing.owner = req.session.userId, and expose a helper that ensures only the owner can edit or delete.
Create simple views under views/auth/ for register/login with method="POST" and fields username, email, and password. Include a logout <form action="/logout" method="POST">.
- Use Joi to validate registration and login payloads before calling the model.
- Handle duplicate key errors (Mongo code
11000) to display friendly “username taken” flashes. - Keep the session secret and MongoDB credentials in environment variables for production.
With these pieces in place, the application gains session-backed authentication and route protection without introducing a heavier framework. Adjust hashing rounds, session cookie attributes, and CSRF protection to fit your deployment environment.