Nigeria-first daily audio news briefing — Next.js PWA + NestJS + PostgreSQL, with LLM summaries and local Kokoro TTS
# Naija Brief
A Nigeria-first morning news briefing that reads itself to you. Every morning it
pulls headlines from Nigerian and world sources across **politics, tech,
business, the stock market, sport and world news**, writes a spoken script with
an LLM (via OpenRouter), voices it with a local neural TTS (Kokoro — free, runs
on your machine), and serves it as an installable web app (PWA) where you can
play the briefing and ask follow-up questions about any story.
## Stack
A TypeScript monorepo (npm workspaces):
| Piece | Tech |
| --- | --- |
| **Frontend** | Next.js 16 (App Router, React 19) as an installable **PWA** |
| **Backend** | NestJS 11 REST API |
| **Storage** | PostgreSQL via TypeORM + migrations (briefs, sections, stories, audio as `bytea`, durable jobs) |
| **LLM** | OpenRouter (default `z-ai/glm-5.2`, swappable) — summaries + drill-down chat |
| **Voice** | `kokoro-js` — Kokoro-82M ONNX TTS, runs locally |
| **Schedule** | `@nestjs/schedule` cron — a fresh brief every morning |
```
apps/
api/ → NestJS backend (news ingestion, LLM, TTS, Postgres, cron)
web/ → Next.js PWA (player, on-air bar, per-story chat)
packages/
shared/ → type-only contracts shared by both apps
```
## Prerequisites
- Node.js 22 (see `.node-version` — CI, Render and the Docker image all read it)
- PostgreSQL running locally (or a `DATABASE_URL`)
- An OpenRouter API key —
## Setup
```bash
npm install
createdb naija_brief # or set DATABASE_URL / PG* vars in .env
cp .env.example .env # then paste your OPENROUTER_API_KEY
npm run migration:run -w apps/api # create the schema (briefs, sections, stories, jobs)
```
> **Already have a database from an earlier build?** It was created by TypeORM
> auto-sync, which is now off by default. Give it the new `generation_jobs` table
> either by starting the API once with `DB_SYNC=1` (additive — leaves your data),
> or, since briefs are regenerated daily, reset for a clean migration baseline:
> …