A clone of TabNews, built as a personal study project with a focus on production-grade engineering practices over feature count — what the project calls zero vibe-coding: no copy-paste fixes that "just work," no shortcuts that wouldn't survive a code review.
The project is delivered milestone by milestone. Full board: GitHub Milestones.
- Milestone 0 — Em construção: completed. Domain configured, code style and editor settings locked in.
- Milestone 1 — Fundação: in progress (4 of 9 issues closed).
- Runtime: Node.js (LTS Hydrogen) · Framework: Next.js 13 (Pages Router) · UI: React 18
- Database: PostgreSQL 16, run locally via Docker Compose
- Migrations:
node-pg-migrate - Tests: Jest, executed against a real database (no mocks)
- CI: GitHub Actions — tests + Prettier on every pull request
- Deploy: Vercel
The bullets below describe choices made in the code and why they were made that way.
npm test boots a real PostgreSQL container, starts the Next.js dev server, waits for the API to report healthy via /api/v1/status, and only then runs Jest. Tests hit real HTTP endpoints and real SQL. Mocked databases pass too easily and miss the bugs that actually break production — broken migrations, connection leaks, malformed queries.
The test orchestrator (tests/orchestrator.js) uses async-retry to poll the readiness endpoint up to 100 times before giving up, so the runner tolerates startup races on slow machines and in CI.
pages/api/v1/status/index.js returns the Postgres version, max_connections, and current open_connections. It serves two purposes: a real readiness probe for the test orchestrator, and an honest "is the database actually reachable?" check for production. The response nests database details under a dependencies key so future dependencies (cache, queue, etc.) can be added without breaking the shape.
pages/api/v1/migrations/index.js wraps node-pg-migrate:
GET /api/v1/migrationsruns the migrator withdryRun: trueand returns the list of pending migrations — a preview before commit.POST /api/v1/migrationsapplies them, returning201if any ran and200if none were pending.- Anything other than
GETorPOSTreturns405.
A dedicated pg client is opened per request and explicitly closed in finally. In serverless environments, leaked connections are how you find out at 3 a.m. that your pool is too small.
Two GitHub Actions workflows, both triggered on pull_request: tests.yaml runs Jest and linting.yaml runs Prettier. Splitting them means a failing test doesn't hide a formatting regression and vice versa, and either can be re-run independently.
infra/database.js enables SSL only when NODE_ENV === "production". Local development stays simple; production stays correct. One line of code, but the kind of mismatch that costs an afternoon when it's wrong.
npm test uses concurrently to run next dev and jest together, hiding the Next.js output and tearing both down when Jest finishes (-k -s command-jest). Combined with the retry-based readiness check, the result is a single command that brings up everything, runs the suite, and exits cleanly — no manual setup steps.
Requires Node.js (LTS Hydrogen) and Docker.
npm install
npm run dev # boots Postgres, waits for it, applies migrations, starts Next.js
npm test # runs the full integration test suiteOther useful scripts:
npm run services:up # start the database container only
npm run services:down # stop and remove it
npm run migration:create # scaffold a new migration file
npm run migration:up # apply pending migrations against .env.development
npm run lint:prettier:fix # auto-format the codebasepages/api/v1/ HTTP endpoints (status, migrations)
infra/database.js Postgres client factory + query wrapper
infra/migrations/ node-pg-migrate migration files
infra/compose.yaml Local Postgres definition
infra/scripts/ Operational scripts (e.g. wait-for-postgres)
tests/integration/ Jest tests against real endpoints
tests/orchestrator.js Readiness probe used by the test runner
.github/workflows/ CI: tests + linting on every PR