A minimal full-stack inventory app: search, add, and adjust product stock backed by MongoDB Atlas, served through Next.js App Router API routes, and rendered with a single React 19 client component.
- Live search — typing into the search box queries MongoDB's
$textoperator (text index acrossname/quantity/price) and renders matches in a dropdown. - Inline quantity adjust —
+/−buttons in the dropdown callupdateOneto change stock without leaving the page. - Add product form — name / quantity / price, posted to
/api/productand persisted withinsertOne. - Stock table — every product in the collection, with a running quantity total in the footer.
| Layer | Tool |
|---|---|
| Framework | Next.js 15.5 (App Router) |
| UI | React 19 + Tailwind CSS v4 |
| Database | MongoDB Atlas (SRV connection) |
| Driver | mongodb v6.20 (official Node.js driver — not Mongoose) |
| Fonts | Geist + Geist Mono (via next/font) |
| Hosting | Vercel |
FlowVentry-Inventory-Management/
├── lib/
│ └── mongodb.js Cached MongoClient singleton (env-var driven)
├── app/
│ ├── layout.js Root layout + font setup
│ ├── page.js Single-page client UI (search, add, stock table)
│ ├── globals.css Tailwind base + custom styles
│ ├── components/
│ │ ├── Header.js Top nav
│ │ └── Footer.js Footer with social icons
│ └── api/
│ ├── product/route.js GET (list) + POST (insert)
│ ├── search/route.js GET — `$text` aggregation search
│ └── update/route.js POST — quantity +/− via `updateOne`
└── public/ Static assets (logo, banner)
- Cached MongoClient.
lib/mongodb.jskeeps oneclientPromiseat module scope and pins it onglobalThisin development so Next.js HMR doesn't spawn a new client on every code edit. Production skips the global cache because modules load once. Replaces a previous per-requestnew MongoClient()pattern that opened and closed a TCP connection on every API call. - Search via
$textaggregation.aggregate([{ $match: { $text: { $search: query } } }])runs against a compound text index onname,quantity, andprice. Using aggregation (instead of a plainfind) leaves room to extend the pipeline later — sort bytextScore, add fuzzy matching, filter by category — without reshaping the call site. - One client component.
app/page.jscarries the entire user-facing app: 8useStateslots for form state, loading flags, the dropdown, and the active query. Server components are used only for the layout shell, header, and footer. - No ORM. The MongoDB Node driver is used directly. The only enforced shape is the text index.
- Env-var configuration.
MONGODB_URIandMONGODB_DBare read fromprocess.env. The check is deferred to first request, sonext buildsucceeds without runtime env vars set (route handlers don't fire at build time).
| Method | Path | Body / Query | Returns |
|---|---|---|---|
GET |
/api/product |
— | { success, allProducts: [] } |
POST |
/api/product |
{ name, quantity, price } |
{ product, ok: true } |
GET |
/api/search |
?query=<term> |
{ allProducts: [] } — matched via $text |
POST |
/api/update |
{ action: "plus"|"minus", name, initialQuantity } |
{ success, message } |
