REST API for a blog: Express, PostgreSQL, Prisma 7, Zod, JWT, layered routes/controllers/services, Jest (HTTP checks via Node fetch + ephemeral server), and Docker Compose.
- Node.js 22+ (includes npm; Prisma 7’s dependency tree expects Node ≥ 22)
- npm or Yarn 1.x (Classic) for installing dependencies and running scripts
- PostgreSQL 14+ (local or container)
git clone https://github.com/WorkuMekuriya/Blog-API.git
cd Blog-APIThen continue with Environment variables and Local development below.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL URL (also read by prisma.config.ts for migrations). |
JWT_SECRET |
Yes | Secret used to sign JWT access tokens. |
PORT |
No | HTTP port (default 3000). |
NODE_ENV |
No | Set to **production** in live deployments (e.g. trust proxy for rate limits behind a load balancer). |
Copy the example file and edit values:
cp .env.example .envUse Yarn or npm — script names are the same (package.json → scripts). With npm, run npm install once, then npm run <name> (e.g. npm run dev). The test script is also available as **npm test**.
yarn install # or: npm install
yarn prisma:generate # or: npm run prisma:generate
yarn prisma:migrate # or: npm run prisma:migrate — or prisma:deploy against an existing DB
yarn db:seed # or: npm run db:seed — optional: default user admin@example.com / Password123!
yarn dev # or: npm run dev- Health:
GET http://localhost:3000/health
yarn install # or: npm install
yarn prisma:generate # or: npm run prisma:generate
yarn build # or: npm run build
yarn prisma:deploy # or: NODE_ENV=production npm run prisma:deploy
NODE_ENV=production node dist/server.jsBuild and run the API plus PostgreSQL:
# Optional: enforce a strong JWT secret (recommended)
export JWT_SECRET="$(openssl rand -hex 32)"
docker compose build --no-cache blog_api
docker compose up- API:
http://localhost:3000 - Postgres is exposed on localhost:5432 for tooling (user/password/db:
blog/blog/blog). - Default login after seed:
admin@example.com/Password123!.
| Area | Window | Max (per IP) |
|---|---|---|
GET /health |
1 minute | 60 |
POST /auth/login |
15 minutes | 30 |
GET /articles, GET /articles/:id |
1 minute | 120 |
Authenticated POST / PUT / DELETE on /articles are not covered by these public limiters.
In **NODE_ENV=test**, limits are skipped so Jest stays deterministic.
Behind a reverse proxy, the app sets **trust proxy** when NODE_ENV=production so the client IP used for limits is correct.
yarn test # or: npm test| Method | Path | Auth | Body |
|---|---|---|---|
GET |
/health |
No | — |
POST |
/auth/login |
No | { "email": "string", "password": "string" } |
GET |
/articles |
No (pagination: page, limit) |
— |
GET |
/articles/:id |
No | — |
POST |
/articles |
Bearer JWT | { "title": "string", "content": "string" } |
PUT |
/articles/:id |
Bearer JWT | { "title": "string", "content": "string" } |
DELETE |
/articles/:id |
Bearer JWT | — |
Login response: { "token": "<jwt>" }. Send Authorization: Bearer <jwt> on protected routes.
src/routes,src/controllers,src/services— HTTP layeringsrc/schemas— Zod validationsrc/middleware— auth, errors, rate limitsprisma/— schema and migrations