MongoDB Project: Product API with Express

This MongoDB project puts everything together: a small REST API for products built with Express and Mongoose. It covers the CRUD operations, query operators, sorting, pagination and validation you learned in this tutorial.

Project Setup

mkdir product-api && cd product-api
npm init -y
npm install express mongoose
added 85 packages in 4s

The Server

One file defines the model and five routes. Query parameters drive filtering and paging.

import express from 'express'
import mongoose from 'mongoose'

await mongoose.connect(process.env.MONGODB_URI ?? 'mongodb://127.0.0.1:27017/shop')
const Product = mongoose.model('Product', new mongoose.Schema({
  name: { type: String, required: true },
  category: String,
  price: { type: Number, min: 0 },
  stock: { type: Number, default: 0 },
}))

const app = express()
app.use(express.json())

app.get('/products', async (req, res) => {
  const { category, maxPrice, page = 1 } = req.query
  const filter = {}
  if (category) filter.category = String(category)
  if (maxPrice) filter.price = { $lte: Number(maxPrice) }
  const items = await Product.find(filter).sort({ price: 1 })
    .skip((page - 1) * 10).limit(10).lean()
  res.json(items)
})

app.post('/products', async (req, res) => {
  try { res.status(201).json(await Product.create(req.body)) }
  catch (e) { res.status(400).json({ error: e.message }) }
})

app.patch('/products/:id', async (req, res) => {
  const p = await Product.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true })
  p ? res.json(p) : res.sendStatus(404)
})

app.delete('/products/:id', async (req, res) => {
  const p = await Product.findByIdAndDelete(req.params.id)
  res.sendStatus(p ? 204 : 404)
})

app.listen(3000, () => console.log('API on http://localhost:3000'))
API on http://localhost:3000

Trying the API

curl -X POST localhost:3000/products -H "Content-Type: application/json" \
  -d '{"name":"Green Tea","category":"Beverages","price":6,"stock":300}'
curl "localhost:3000/products?category=Beverages&maxPrice=10"
{"name":"Green Tea","category":"Beverages","price":6,"stock":300,"_id":"6720b1...","__v":0}
[{"_id":"6720b1...","name":"Green Tea","category":"Beverages","price":6,"stock":300,"__v":0}]

Ideas to Extend It

  • Add an index on { category: 1, price: 1 } and check it with explain()
  • Add an /stats route using an aggregation pipeline grouped by category
  • Add an Order model with populate() and a transaction that reduces stock
  • Validate ObjectId params with mongoose.isValidObjectId before querying

Frequently asked questions

What is the MERN stack?

MongoDB, Express, React and Node.js. This project is the M and E part: the API that a React front end would call.

Why use String() and Number() on query parameters?

Query values come from users. Converting them prevents operator injection and ensures MongoDB compares the right types.

Where should I deploy a MongoDB project?

Use MongoDB Atlas for the database and a Node host such as Render, Railway, Fly.io or a VPS for the API, keeping the URI in an environment variable.