Project Setup
Create the two halves of the app — a frontend (the UI) and a backend(the API) — then install every package you'll need. Pick the tab that matches your stack choice.
One Repo, Two Apps
We keep the frontend and backend side-by-side in a single folder. The database is set up on the next page (Docker for Postgres, Atlas for MongoDB).
inventory-app/
├─ web/ # frontend — Next.js OR React + Vite
├─ server/ # backend — Express OR NestJS
├─ docker-compose.yml # Runs Postgres & services (added in Step 7)
└─ README.md$ mkdir inventory-app
$ cd inventory-appserver/ is optional. This guide keeps a standalone backend so the same API works for both Next.js and React + Vite. If you prefer Next-only, put the backend code in web/app/api/* instead — the logic is identical.Create the Frontend
Both options use React under the hood. Next.js (nextjs.org) is a full framework; React + Vite (vite.dev) is a minimal SPA (single-page app) setup.
$ npx create-next-app@latest web
# TypeScript? Yes · ESLint? Yes · Tailwind CSS? Yes · App Router? Yes
$ cd web && npm run dev # → http://localhost:3000Tailwind is wired up automatically. State (the logged-in user, the product list) will live in Zustand:
$ npm install zustandnpx shadcn@latest init. The components are copied into your project, so you own and can edit them.Create the Backend
The API holds all the real logic — auth, validation, database access, email. Express (expressjs.com) is minimal; NestJS (nestjs.com) is structured.
$ mkdir server && cd server
$ npm init -y
$ npm install express
$ npm install -D typescript tsx @types/express @types/node
$ npx tsc --init # creates tsconfig.jsonA minimal server to confirm it runs:
import express from "express";
const app = express();
app.use(express.json());
app.get("/api/health", (_req, res) => res.json({ status: "ok" }));
app.listen(4000, () => console.log("API on http://localhost:4000"));"scripts": {
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js"
}Install the Core Packages
These power authentication, validation, email, and database access. Run inside server/.
Shared by every stack
$ npm install bcryptjs jsonwebtoken zod nodemailer
$ npm install -D @types/bcryptjs @types/jsonwebtoken @types/nodemailer- bcryptjs — hash passwords safely.
- jsonwebtoken — sign/verify JWTs (login + email-verify tokens).
- zod — validate incoming request bodies.
- nodemailer — send verification & low-stock emails over SMTP.
Express adds
Express needs a few middlewares that NestJS already bundles:
$ npm install cors cookie-parser dotenv
$ npm install -D @types/cors @types/cookie-parserDatabase driver — pick one
Postgres uses TypeORM; MongoDB uses Mongoose. We install the full setup on the next page; the packages are:
$ npm install typeorm reflect-metadata pg$ npm install @nestjs/typeorm typeorm pg
$ npm install @nestjs/config @nestjs/jwtEnvironment Variables
Secrets (DB password, JWT secret, SMTP login) never go in code. Put them in a .env file in server/ and add .env to .gitignore.
# --- App ---
PORT=4000
CLIENT_URL="http://localhost:3000" # 5173 for Vite
# --- Database (use ONE) ---
DATABASE_URL="postgresql://postgres:secret@localhost:5432/inventory"
# DATABASE_URL="mongodb+srv://<username>:<password>@cluster0.xxxx.mongodb.net/inventory?retryWrites=true&w=majority"
# --- Auth ---
JWT_SECRET="a-long-random-string-change-me"
# --- SMTP (from Mailtrap or Gmail App Password) ---
SMTP_HOST="sandbox.smtp.mailtrap.io"
SMTP_PORT=2525
SMTP_USER="your-user"
SMTP_PASS="your-pass"
MAIL_FROM="Inventory MS <no-reply@inventory.app>".gitignore with node_modules and .env in it. Commit a .env.example (same keys, blank values) so teammates know what to fill in.Run Both Dev Servers
Open two bash terminals. Frontend in one, backend in the other.
# bash 1
$ cd web && npm run dev
# bash 2
$ cd server && npm run dev- ✓Frontend loads (localhost:3000 or :5173)
- ✓Backend responds at
/api/healthwith{"status":"ok"} - ✓All core packages installed without errors
- ✓
.envcreated and git-ignored