News that matter to humanity. Curated with care by AI.
Crawls news sources, assesses relevance with structured AI analysis, and publishes curated stories.
- Frontend: Vite + React 18 + TypeScript + Tailwind CSS
- Backend: Express + TypeScript + LangChain + OpenAI
- Database: PostgreSQL + pgvector (Prisma ORM)
- Deployment: Render.com
- Node.js 18+ and npm
- Docker (for the local PostgreSQL + pgvector database)
- OpenAI API key
-
Clone the repository:
git clone https://github.com/OdinMB/actually-relevant.git cd actually-relevant -
Install dependencies:
cd client && npm install cd ../server && npm install
-
Start the database (PostgreSQL 17 + pgvector on
localhost:5433, databaseactually_relevant_dev):docker compose up -d
-
Configure environment variables:
cp server/env.example server/.env # then fill in the values cp client/env.example client/.env # Required at minimum: DATABASE_URL, OPENAI_API_KEY, JWT_SECRET, FRONTEND_URL. # The template's DATABASE_URL already points at the Docker database # (postgresql://ardev:...@localhost:5433/actually_relevant_dev, see docker-compose.yml). # See server/src/config.ts for all available settings and their defaults.
-
Start development servers:
# Terminal 1 — Frontend (localhost:5173) cd client && npm run dev # Terminal 2 — Backend (localhost:3001) cd server && npm run dev
Starting the backend first applies any pending migrations and regenerates the Prisma client if the schema changed (
npm run db:prepare, also runnable on its own). It only does this whenDATABASE_URLpoints at this machine (localhost,127.0.0.1or::1); for any other host it warns and skips. SetSKIP_DB_PREPARE=1to skip it. A failing migration stops the backend from starting. See.context/database-migrations.md.
This project requires three services on Render: a managed PostgreSQL database, an Express backend (web service), and a React frontend (static site). The backend and frontend run on separate origins.
- Create a new PostgreSQL instance on Render
- Note the Internal Database URL (used by the backend service)
- Enable pgvector — connect to the database and run:
CREATE EXTENSION IF NOT EXISTS vector;
| Field | Value |
|---|---|
| Root Directory | server |
| Build Command | npm install --include=dev && npx prisma generate && npx prisma migrate deploy && npm run build |
| Start Command | npm start |
| Health Check Path | /health |
The build generates the Prisma client, applies any pending database migrations, and compiles TypeScript. Migrations run automatically on every deploy via prisma migrate deploy, which is a no-op when there are no pending migrations. NODE_ENV=production makes a plain npm install skip devDependencies, so the command installs them with --include=dev, and the build script installs them again itself and compiles with the lockfile's TypeScript (see .context/deployment.md).
Environment Variables:
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Internal PostgreSQL connection string from step 1 |
OPENAI_API_KEY |
Yes | OpenAI API key for LLM analysis |
FRONTEND_URL |
Yes | Frontend URL for CORS (e.g. https://actuallyrelevant.news) |
JWT_SECRET |
Yes | Random string (32+ chars) for signing auth tokens |
NODE_ENV |
Yes | Set to production (enables secure cross-origin cookies) |
PORT |
No | Render sets this automatically (defaults to 10000) |
PUBLIC_API_KEY |
No | Static API key for public consumers (mobile apps, etc.) |
LOG_LEVEL |
No | Logging verbosity (default: info) |
Architecture notes:
- Cron jobs run in-process via node-cron — no separate worker service is needed. Job configuration lives in the
job_runsdatabase table and is managed from the admin dashboard. - Graceful shutdown handles
SIGTERM(sent by Render on deploy) by draining in-flight LLM tasks before disconnecting from the database. - Reverse proxy trust is configured (
trust proxy: 1) for correct client IP detection behind Render's load balancer. - Cross-origin cookies use
sameSite: 'none'+secure: truein production, which is required because the frontend and backend are on different Render origins. This is whyNODE_ENV=productionis mandatory.
| Field | Value |
|---|---|
| Root Directory | client |
| Build Command | npm install && npm run build |
| Publish Directory | dist |
The build script installs devDependencies itself (npm install --include=dev, see .context/deployment.md), then type-checks, bundles with Vite, and prerenders public routes using Puppeteer. Render's build environment includes Chromium, so prerendering works without extra setup.
Rewrite rules: Add these rewrites in the Render dashboard in this exact order (Render evaluates rules top-to-bottom, first match wins):
| Source | Destination | Action |
|---|---|---|
/sitemap.xml |
https://<backend-service>.onrender.com/api/sitemap.xml |
Rewrite |
/* |
/index.html |
Rewrite |
Order is critical: The /sitemap.xml rule must appear before the catch-all /* rule. If reversed, the catch-all matches first and serves the SPA shell, resulting in a 404.
The sitemap rewrite proxies requests to the backend, which generates the sitemap dynamically from published stories. No static sitemap.xml file should exist in client/public/ — Render serves static files before applying rewrite rules.
Environment Variables:
| Variable | Required | Description |
|---|---|---|
VITE_API_URL |
Yes | Backend URL (e.g. https://api.actuallyrelevant.news) |
- Set backend
FRONTEND_URLto match the frontend URL (and vice versa forVITE_API_URL) - Database migrations run automatically during the build step — no manual action needed
- Create the first admin user from the Render shell:
npx tsx src/scripts/create-admin.ts
- Add the
/sitemap.xmlrewrite rule to the static site (see Frontend section above) - Verify the health endpoint:
curl https://<backend-url>/health - Verify the sitemap:
curl https://<frontend-url>/sitemap.xml
actually-relevant/
├── client/ # React frontend
│ ├── src/ # Source code
│ ├── scripts/ # Build scripts (sitemap, images)
│ ├── dist/ # Built output (gitignored)
│ └── package.json
├── server/ # Express backend
│ ├── src/ # Source code
│ ├── prisma/ # Database schema and migrations
│ ├── dist/ # Built output (gitignored)
│ └── package.json
├── shared/ # Shared types and constants
├── scripts/ # Build helper shared by client and server (devDependency install)
├── .context/ # Subsystem docs (behavior and implementation) and the decision log
├── CONTRIBUTING.md # Contribution guidelines
├── LICENSE # AGPL v3
└── README.md # This file
Contributions are welcome! See CONTRIBUTING.md for guidelines, including how to set up the development environment, submit pull requests, and the project's lightweight contributor agreement.
Actually Relevant is actively seeking a long-term institutional owner in journalism, civic tech, or effective altruism. If your organization could give this project a home, visit actuallyrelevant.news/stewardship to learn more.
This project is licensed under the GNU Affero General Public License v3.0. Organizations interested in running actuallyrelevant.news as a long-term steward can receive more accommodating license terms — see Stewardship.
This software marks the content it generates as AI-generated in machine-readable form: the aiGenerated block on story objects in the public API, data-ai-generated attributes on story pages, and XMP metadata in the carousel images and PDF. See .context/ai-transparency.md.
Please do not intentionally remove or tamper with the machine-readable markings (such as metadata or watermarks) that identify content generated by this software as AI-generated.
Check the build logs. Common issues:
- Missing
Root Directorysetting on Render - Node version mismatch — add
enginesto package.json if needed - Missing
npx prisma generatebefore server build
- Verify
FRONTEND_URLis set correctly on the backend - Ensure it matches exactly (including
https://, no trailing slash) - Redeploy after changing environment variables
- Verify
DATABASE_URLis correct - Ensure pgvector extension is installed:
CREATE EXTENSION IF NOT EXISTS vector; - Run migrations:
npx prisma migrate deploy
curl https://your-api-url.onrender.com/health
# Should return: {"status":"ok"}