What you'll build

This is Part 1 of a series where you build a Product Catalog API — a backend service that any frontend (web, mobile, or another service) can call to list products, manage inventory, handle user accounts, and process Paystack payments.

By the end of the series you'll have:

By the end of Part 1 you'll have a running Express server with proper TypeScript setup, structured error handling, and a tested health check endpoint.

Prerequisites

Create the project

mkdir product-catalog-api && cd product-catalog-api
npm init -y
npm install express zod dotenv cors helmet
npm install -D typescript ts-node @types/node @types/express @types/cors nodemon

Create tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "resolveJsonModule": true
  },
  "include": ["src"]
}

Open the project in VS Code:

code .

Tell BlocWeave about the project

Create BLOCWEAVE_PLAN.md:

# Product Catalog API

## Stack
- Node.js 20 + Express + TypeScript
- Supabase (PostgreSQL) for data
- Zod for request validation
- JWT for auth (jsonwebtoken)
- Paystack for payments
- Railway for hosting

## Conventions
- All source in src/
- Routes in src/routes/<name>.router.ts
- Controllers in src/controllers/<name>.controller.ts
- Middleware in src/middleware/
- Shared types in src/types.ts
- Error responses always use { error: string } shape
- Success responses always use { data: T } shape (or { data: T, meta: { total, page, limit } } for lists)
- Env vars: DATABASE_URL, SUPABASE_URL, SUPABASE_SERVICE_KEY, JWT_SECRET, PAYSTACK_SECRET_KEY

Build the server foundation

BlocWeave prompt

Set up the Express server foundation for the product catalog API. 1. src/index.ts — creates the Express app, registers middleware (helmet, cors, express.json()), mounts all routers (just /health for now), starts listening on process.env.PORT ?? 3000, and exports the app for testing. 2. src/middleware/errorHandler.ts — an Express error-handling middleware (4-argument signature). Logs the error. If the error has a statusCode property, use it; otherwise 500. Always responds with { error: error.message }. 3. src/middleware/validate.ts — a middleware factory: validate(schema: ZodSchema) returns an Express middleware that runs schema.safeParse(req.body); if success sets req.body to the parsed data; if fail returns 400 with { error: "Validation failed", details: zodError.errors }. 4. src/routes/health.router.ts — GET /health returns { status: "ok", timestamp: new Date().toISOString(), version: process.env.npm_package_version ?? "0.0.0" }.

Add dev scripts

Update package.json scripts:

{
  "scripts": {
    "dev": "nodemon --exec ts-node src/index.ts --ext ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "type-check": "tsc --noEmit"
  }
}

Start the server

npm run dev

Test the health endpoint:

curl http://localhost:3000/health

You should see:

{ "status": "ok", "timestamp": "2026-06-14T10:00:00.000Z", "version": "1.0.0" }

Run the TypeScript type checker to confirm no errors:

npm run type-check

Create the .env file

PORT=3000
JWT_SECRET=change-this-to-a-long-random-string
SUPABASE_URL=
SUPABASE_SERVICE_KEY=
PAYSTACK_SECRET_KEY=
echo ".env" >> .gitignore

Project structure after Part 1

src/
  middleware/
    errorHandler.ts
    validate.ts
  routes/
    health.router.ts
  index.ts
BLOCWEAVE_PLAN.md
tsconfig.json
package.json
.env

Why Zod? Zod validates that incoming request data matches the shape you expect before it reaches your controller. Without it, a missing field or wrong type can cause a runtime error deep in your code that's hard to debug. Zod catches it at the boundary with a clear error message.

What's next

In Part 2 we connect Supabase, create the products and categories tables, and build the full set of CRUD endpoints — list, get by ID, create, update, and delete.