Well-structured backends separate concerns: routers define URLs, controllers translate HTTP to application calls, services own data access, and middleware handles cross-cutting behavior (validation, 404, errors). Study these patterns to keep files small, testable, and aligned with REST conventions.

Router-level middleware

Matches Router-level middleware: validation and scoped CORS on specific routers.

2 topic(s) on this page.

How to enable CORS — Intermediate — per router

Scope CORS to specific routers when only some paths need cross-origin access.
Learn: Router-scoped CORS limits cross-origin access to specific path prefixes—useful when only public API routes need browser access while admin routes stay same-origin only.

Supplementary examples

CORS on a single router

const cors = require("cors");
const publicRouter = require("./public/public.router");

app.use("/api/public", cors(), publicRouter);

Course example

const router = require("express").Router();
const controller = require("./cors-enabled.controller");
const methodNotAllowed = require("../errors/methodNotAllowed");
const cors = require("cors");
router.use(cors())
router
.route("/:corsId")
.get(controller.read)
.put(controller.update)
.delete(controller.delete)
.all(methodNotAllowed);
router
.route("/")
.get(cors(), controller.list)
.post(controller.create)
.all(methodNotAllowed);
module.exports = router;

Additional references & examples

How to validate request data — Intermediate

Run validation middleware before handlers so invalid body or params are rejected early.
Learn: Validate before controllers run so bad input never reaches the database. Return 400 with a clear message. Chain validators, then a small middleware that checks `validationResult` and stops the request.

Supplementary examples

express-validator chain

const { body, validationResult } = require("express-validator");

const validateCreate = [
  body("name").trim().isLength({ min: 1 }).withMessage("name is required"),
  body("tags").optional().isArray(),
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ error: errors.array() });
    }
    next();
  },
];

Course example

const User = require("./users.model")
async function list(req, res) {
const users = await User.find()
res.send(users);
}
async function userExists(req, res, next) {
const { userId } = req.params;
const foundUser = await User.findOne({"_id":userId});
if (foundUser) {
res.locals.user = foundUser;
return next();
}
next({
status: 404,
message: `User id not found: ${userId}`,
});
};
function read(req, res, next) {
res.json({ data: res.locals.user });
};
function bodyDataHas(propertyName) {
return function (req, res, next) {
const { data = {} } = req.body;
if (data[propertyName]) {
return next();
}
next({
status: 400,
message: `Must include a ${propertyName}`
});
};
}
async function create(req, res) {
const { data: { username, email} = {} } = req.body;
const newUser = new User({
username: username,
email: email,
})
await newUser.save()
res.status(201).json({ data: newUser });
}
async function update(req, res) {
const user = res.locals.user;
const { data: { username, email } = {} } = req.body;
user.username = username;
user.email = email;
await user.save()
res.json({ data: user });
}
async function destroy(req, res) {
const { userId } = req.params;
await User.deleteOne({ _id: userId })
res.sendStatus(204);
}
module.exports = {
list,
read: [userExists, read],
create: [
bodyDataHas("username"),
bodyDataHas("email"),
create
],
update: [
userExists,
bodyDataHas("username"),
bodyDataHas("email"),
update
],
delete: [userExists, destroy],
userExists,
};

Additional references & examples