Chapter 10 of 10
Build a complete Bookstore REST API with CRUD for books, author management, search, and JWT authentication. This is a production-level API structure you can use as a template.
bookstore-api/
โโโ src/
โ โโโ index.js # entry point
โ โโโ db.js # database connection
โ โโโ middleware/
โ โ โโโ auth.js # JWT middleware
โ โ โโโ errorHandler.js
โ โโโ models/
โ โ โโโ Book.js
โ โ โโโ User.js
โ โโโ routes/
โ โโโ auth.js
โ โโโ books.js
โโโ .env
โโโ .gitignore
โโโ package.jsonconst router = require("express").Router();
const Book = require("../models/Book");
const { auth } = require("../middleware/auth");
// GET /api/books โ list with search + pagination
router.get("/", async (req, res, next) => {
try {
const { q, genre, page = 1, limit = 20 } = req.query;
const filter = {};
if (q) filter.$or = [{ title: new RegExp(q, "i") }, { author: new RegExp(q, "i") }];
if (genre) filter.genre = genre;
const [books, total] = await Promise.all([
Book.find(filter).skip((page - 1) * limit).limit(Number(limit)).sort("-createdAt"),
Book.countDocuments(filter),
]);
res.json({ books, total, page: Number(page), pages: Math.ceil(total / limit) });
} catch (err) { next(err); }
});
// GET /api/books/:id
router.get("/:id", async (req, res, next) => {
try {
const book = await Book.findById(req.params.id);
if (!book) return res.status(404).json({ error: "Book not found" });
res.json(book);
} catch (err) { next(err); }
});
// POST /api/books โ protected
router.post("/", auth, async (req, res, next) => {
try {
const book = await Book.create({ ...req.body, addedBy: req.user.id });
res.status(201).json(book);
} catch (err) { next(err); }
});
// PUT /api/books/:id โ protected
router.put("/:id", auth, async (req, res, next) => {
try {
const book = await Book.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
if (!book) return res.status(404).json({ error: "Book not found" });
res.json(book);
} catch (err) { next(err); }
});
// DELETE /api/books/:id โ protected
router.delete("/:id", auth, async (req, res, next) => {
try {
await Book.findByIdAndDelete(req.params.id);
res.sendStatus(204);
} catch (err) { next(err); }
});
module.exports = router;