Chapter 43 — Backend Machine Coding Tasks
📖 What Interviewers Are Testing
- API design — RESTful URLs, correct status codes.
- Folder layering — routes / controllers / services / models.
- Auth handling — JWT, role-based.
- Validation & errors — DTO or schema validation, consistent error format.
- Async correctness —
try/catch,Promise.allwhere appropriate.
Task — JWT-Protected CRUD API for an Items Resource
Spec
POST /auth/register(email, password)POST /auth/login→{ accessToken }GET /items(auth)POST /items(auth, body validation)GET /items/:id(auth)PATCH /items/:id(auth, only owner)DELETE /items/:id(auth, admin only)
📁 Folder Structure
src/
├── app.js
├── server.js
├── config/
│ ├── db.js
│ └── env.js
├── routes/
│ ├── auth.routes.js
│ └── items.routes.js
├── controllers/
│ ├── auth.controller.js
│ └── items.controller.js
├── services/
│ ├── auth.service.js
│ └── items.service.js
├── models/
│ ├── user.model.js
│ └── item.model.js
├── middlewares/
│ ├── auth.middleware.js
│ ├── role.middleware.js
│ ├── validate.middleware.js
│ └── error.middleware.js
├── utils/
│ ├── AppError.js
│ ├── asyncHandler.js
│ └── jwt.js
└── tests/💻 Code — Models
// models/user.model.js
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true, select: false },
role: { type: String, enum: ["admin", "user"], default: "user" },
}, { timestamps: true });
export const User = mongoose.model("User", userSchema);// models/item.model.js
import mongoose from "mongoose";
const itemSchema = new mongoose.Schema({
title: { type: String, required: true, minlength: 2 },
body: { type: String, default: "" },
owner: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
isPublic: { type: Boolean, default: false },
}, { timestamps: true });
itemSchema.index({ owner: 1, createdAt: -1 });
export const Item = mongoose.model("Item", itemSchema);💻 Code — Utils
// utils/AppError.js
export class AppError extends Error {
constructor(status, code, message) {
super(message);
Object.assign(this, { status, code, isOperational: true });
}
}
// utils/asyncHandler.js
export const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// utils/jwt.js
import jwt from "jsonwebtoken";
export const signAccess = (payload) => jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: "15m" });
export const verifyAccess = (token) => jwt.verify(token, process.env.JWT_SECRET);💻 Code — Middleware
// middlewares/auth.middleware.js
import { AppError } from "../utils/AppError.js";
import { verifyAccess } from "../utils/jwt.js";
export function authMiddleware(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith("Bearer ")) throw new AppError(401, "NO_TOKEN", "Missing token");
try {
req.user = verifyAccess(header.slice(7));
next();
} catch {
next(new AppError(401, "BAD_TOKEN", "Invalid or expired token"));
}
}// middlewares/role.middleware.js
import { AppError } from "../utils/AppError.js";
export const requireRole = (role) => (req, res, next) => {
if (req.user?.role !== role) {
return next(new AppError(403, "FORBIDDEN", "Insufficient permissions"));
}
next();
};// middlewares/validate.middleware.js
export const validate = (schema) => (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: { code: "VALIDATION", details: result.error.flatten() } });
}
req.body = result.data;
next();
};// middlewares/error.middleware.js
export function errorHandler(err, req, res, next) {
console.error(err);
res.status(err.status || 500).json({
error: { code: err.code || "INTERNAL", message: err.message },
});
}💻 Code — Auth Service & Controller
// services/auth.service.js
import bcrypt from "bcrypt";
import { User } from "../models/user.model.js";
import { AppError } from "../utils/AppError.js";
import { signAccess } from "../utils/jwt.js";
export const AuthService = {
async register({ email, password }) {
if (await User.findOne({ email })) {
throw new AppError(409, "EXISTS", "Email already in use");
}
const hashed = await bcrypt.hash(password, 10);
const user = await User.create({ email, password: hashed });
return { id: user.id, email: user.email };
},
async login({ email, password }) {
const user = await User.findOne({ email }).select("+password");
if (!user) throw new AppError(401, "BAD_CREDS", "Invalid credentials");
const ok = await bcrypt.compare(password, user.password);
if (!ok) throw new AppError(401, "BAD_CREDS", "Invalid credentials");
const accessToken = signAccess({ sub: user.id, role: user.role });
return { accessToken };
},
};// controllers/auth.controller.js
import { AuthService } from "../services/auth.service.js";
import { asyncHandler } from "../utils/asyncHandler.js";
export const AuthController = {
register: asyncHandler(async (req, res) => {
const user = await AuthService.register(req.body);
res.status(201).json(user);
}),
login: asyncHandler(async (req, res) => {
const tokens = await AuthService.login(req.body);
res.json(tokens);
}),
};💻 Code — Items Service & Controller
// services/items.service.js
import { Item } from "../models/item.model.js";
import { AppError } from "../utils/AppError.js";
export const ItemsService = {
list({ page = 1, limit = 20 }) {
const skip = (page - 1) * limit;
return Promise.all([
Item.find().sort("-createdAt").skip(skip).limit(+limit),
Item.countDocuments(),
]).then(([data, total]) => ({ data, meta: { page: +page, limit: +limit, total } }));
},
create(userId, dto) {
return Item.create({ ...dto, owner: userId });
},
async get(id) {
const item = await Item.findById(id);
if (!item) throw new AppError(404, "NOT_FOUND", "Item not found");
return item;
},
async update(id, userId, patch) {
const item = await this.get(id);
if (item.owner.toString() !== userId) {
throw new AppError(403, "NOT_OWNER", "Only the owner can update");
}
Object.assign(item, patch);
await item.save();
return item;
},
async remove(id) {
const result = await Item.findByIdAndDelete(id);
if (!result) throw new AppError(404, "NOT_FOUND", "Item not found");
},
};// controllers/items.controller.js
import { ItemsService } from "../services/items.service.js";
import { asyncHandler } from "../utils/asyncHandler.js";
export const ItemsController = {
list: asyncHandler(async (req, res) => res.json(await ItemsService.list(req.query))),
create: asyncHandler(async (req, res) => res.status(201).json(await ItemsService.create(req.user.sub, req.body))),
get: asyncHandler(async (req, res) => res.json(await ItemsService.get(req.params.id))),
update: asyncHandler(async (req, res) => res.json(await ItemsService.update(req.params.id, req.user.sub, req.body))),
remove: asyncHandler(async (req, res) => { await ItemsService.remove(req.params.id); res.status(204).end(); }),
};💻 Code — Routes
// routes/auth.routes.js
import { Router } from "express";
import { z } from "zod";
import { AuthController } from "../controllers/auth.controller.js";
import { validate } from "../middlewares/validate.middleware.js";
const RegisterSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
const router = Router();
router.post("/register", validate(RegisterSchema), AuthController.register);
router.post("/login", validate(RegisterSchema), AuthController.login);
export default router;// routes/items.routes.js
import { Router } from "express";
import { z } from "zod";
import { ItemsController } from "../controllers/items.controller.js";
import { authMiddleware } from "../middlewares/auth.middleware.js";
import { requireRole } from "../middlewares/role.middleware.js";
import { validate } from "../middlewares/validate.middleware.js";
const CreateItem = z.object({
title: z.string().min(2),
body: z.string().optional(),
isPublic: z.boolean().optional(),
});
const UpdateItem = CreateItem.partial();
const router = Router();
router.use(authMiddleware);
router.get ("/", ItemsController.list);
router.post ("/", validate(CreateItem), ItemsController.create);
router.get ("/:id", ItemsController.get);
router.patch ("/:id", validate(UpdateItem), ItemsController.update);
router.delete("/:id", requireRole("admin"), ItemsController.remove);
export default router;💻 Code — App & Server
// app.js
import express from "express";
import helmet from "helmet";
import cors from "cors";
import morgan from "morgan";
import authRoutes from "./routes/auth.routes.js";
import itemRoutes from "./routes/items.routes.js";
import { errorHandler } from "./middlewares/error.middleware.js";
const app = express();
app.use(helmet());
app.use(cors());
app.use(express.json({ limit: "1mb" }));
app.use(morgan("combined"));
app.use("/api/auth", authRoutes);
app.use("/api/items", itemRoutes);
app.use(errorHandler);
export default app;// server.js
import "dotenv/config";
import app from "./app.js";
import mongoose from "mongoose";
const port = process.env.PORT || 3000;
await mongoose.connect(process.env.MONGO_URL);
app.listen(port, () => console.log(`API on :${port}`));🧪 Quick cURL Tests
# Register
curl -X POST localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"a@b.com","password":"secret12"}'
# Login → get access token
TOKEN=$(curl -s -X POST localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"a@b.com","password":"secret12"}' | jq -r .accessToken)
# Create
curl -X POST localhost:3000/api/items \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Hello","body":"World"}'
# List
curl -H "Authorization: Bearer $TOKEN" localhost:3000/api/items🎯 What to Say While Coding
- "I'll set up the folder structure first so we have a clear separation."
- "All routes go through
authMiddleware, and admin-only routes layer on a role guard." - "I'm using Zod for body validation — keeps it close to the route definition."
- "Errors are normalized through a single error middleware so the frontend has a consistent shape."
← Frontend Tasks | Index | Next: System Design (Authentication) →