Simple CRUD API for a product catalog built with Fastify and in-memory storage. https://github.com/AlreadyBored/nodejs-assignments/blob/main/assignments-v2/03-crud-api/assignment.md
- Fastify-based REST API
- Full CRUD for products
- Request validation with Zod
- Human-friendly
404and500handlers - Environment configuration via
.env - Development and production modes
- Optional multi-process mode (
start:multi) with:- Node.js Cluster workers
- Round-robin load balancer
- Shared in-memory state via IPC
- API tests with
node:test+fastify.inject()
- Node.js
>=24.10.0 - TypeScript
- Fastify
- Zod
- Dotenv
src/
app.ts # Fastify app + global handlers
server.ts # Single-instance server bootstrap
cluster.ts # Optional cluster mode + load balancer
routes/
products.ts # CRUD routes
products.test.ts # API tests
schemas/
product.ts # Zod schemas
services/
product-store.ts # In-memory store + IPC-backed store
types/
product.ts # Product types
- Install Node.js
24.10.0or newer. - Ensure
npmis available.
Check versions:
node -v
npm -v- Install dependencies:
npm install- Create
.envfrom example:
cp .env.example .env.envshould contain:
PORT=4000npm run start:dev— run app in development mode (tsx watch)npm run build— compile TypeScript todist/npm run start:prod— build and run compiled servernpm run start:multi— build and run cluster mode (load balancer + workers)npm test— run API tests
npm run start:devServer starts on:
http://localhost:4000
npm run start:prodnpm run start:multiExpected behavior:
- Load balancer listens on
PORT(default:4000) - Workers listen on
PORT + 1 ... PORT + n n = availableParallelism() - 1(at least 1 worker)- Requests to
localhost:4000are forwarded round-robin to workers
Base URL:
http://localhost:4000/api/products
{
"id": "uuid",
"name": "string",
"description": "string",
"price": 99.99,
"category": "electronics | books | clothing | ...",
"inStock": true
}GET /api/products200→ array of products
curl -i http://localhost:4000/api/productsGET /api/products/:productId200→ product object400→ invalid UUID404→ product not found
curl -i http://localhost:4000/api/products/<PRODUCT_ID>POST /api/products201→ created product400→ invalid/missing required fields
curl -i -X POST http://localhost:4000/api/products \
-H "Content-Type: application/json" \
-d '{
"name": "Phone",
"description": "Smartphone",
"price": 999.99,
"category": "electronics",
"inStock": true
}'PUT /api/products/:productId200→ updated product400→ invalid UUID or invalid body404→ product not found
curl -i -X PUT http://localhost:4000/api/products/<PRODUCT_ID> \
-H "Content-Type: application/json" \
-d '{
"name": "Phone Pro",
"description": "Updated model",
"price": 1099.99,
"category": "electronics",
"inStock": false
}'DELETE /api/products/:productId204→ deleted400→ invalid UUID404→ product not found
curl -i -X DELETE http://localhost:4000/api/products/<PRODUCT_ID>For unknown routes, server returns:
404- human-friendly message
Example:
curl -i http://localhost:4000/some-non/existing/resource- Route-level validation and domain errors return
400/404 - Global Fastify error handler returns
500 - Not found handler returns
404for unknown endpoints
Run tests:
npm testCurrent tests cover:
- Full E2E scenario in one test:
GET empty→POST→GET by id→PUT→DELETE→GET deleted (404)
- Invalid UUID scenario (
GET/PUT/DELETE->400) - Validation + not found (
POST invalid body -> 400,GET unknown UUID -> 404)
Start cluster:
npm run start:multiThen verify cross-worker state:
POSTtohttp://localhost:4001/api/productsGETsame ID fromhttp://localhost:4002/api/products/:id→ expect200DELETEsame ID onhttp://localhost:4003/api/products/:id→ expect204GETsame ID again onhttp://localhost:4001/api/products/:id→ expect404
- Data is in-memory and resets after process restart.
.envis excluded from git; use.env.exampleas template.