Generated Computers
Locals: an Apple Silicon engine that runs local LLMs up to 3x faster. A product of Generated Computers.
翻譯準備中
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Self-hosted headless CMS for frontend developers. Built with NestJS, Next.js, and PostgreSQL.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗WordPress — You're maintaining PHP, plugins, and a theme engine for a simple blog.
Contentful — $300/mo for features your project will use maybe 20% of.
Strapi — Spent 4 hours fighting Docker, Node version conflicts, and a half-broken admin UI.
Sanity — Vendor lock-in, CDN pricing surprises, and a learning curve nobody warned you about.
NodePress is different. It's a headless CMS built specifically for frontend developers who need a backend for their clients — not a platform to build a career on.
npx create-nodepress-app my-project
That's it. The CLI scaffolds the project, generates cryptographically secure secrets, installs all dependencies, and tells you exactly what to do next.
Open http://localhost:5173 → fill in a form → you have a working CMS with a REST API, GraphQL API, admin panel, and real-time WebSocket events.
On a server (Linux + Docker), one line — with automatic HTTPS when you pass a domain:
curl -fsSL https://raw.githubusercontent.com/buildwithkode/nodepress/main/scripts/install.sh | DOMAIN=cms.example.com bash
Putting it live for a client? The deploy guides walk through it step by step: your own server (DigitalOcean, Hetzner, Lightsail…) with Docker or without Docker, Railway or DigitalOcean App Platform, and a go-live checklist. Shared hosting (cPanel) can't run NodePress.
From your frontend: npm install @nodepress-cms/client — see the client README. From AI assistants: npx create-nodepress-app mcp (MCP server).
| Feature | What it does |
|---|---|
| Visual content type builder | Define your schema in the UI — no code, no config files |
| Auto-generated REST API | Every content type instantly gets GET, POST, PUT, DELETE endpoints |
| Advanced REST Filter Engine | Nested filtering with operators: where[price][gte]=100, where[tag][in]=a,b, OR & AND logic |
| GraphQL API | Apollo Server with full queries + mutations — entries, content types, webhooks, media |
| Multi-locale (i18n) | Per-entry locale field with ?locale= filtering on all endpoints |
| Side-by-Side Translation Studio | Dual-pane comparative translation workspace with 1-click "Copy Empty Fields from Reference" |
| Content relations | Link entries across types — ?populate=author,author.company ($O(\text{depth})$ batched lookups) |
| Next.js Live Draft Mode | Built-in Next.js App Router Draft Mode integration, signed HMAC tokens & preview banner |
| Field-Level Security (RBAC/PBAC) | Granular readRoles (strip fields from public responses) & writeRoles (protect fields from edits) |
| Bulk operations | Publish, archive, or delete hundreds of entries in one click |
| Export / Import | Download all entries as JSON, re-import to seed or migrate |
| Version history | Every entry save is versioned — restore any previous state in one click |
| Schema versioning | Content type changes are tracked — see exactly what changed and when |
| Field data migrations | Rename or retype a field and existing entries follow — previewed before you save |
| Conditional fields | "Show only if…" rules between fields; hidden fields are never required |
| Reusable components |
| Feature | What it does |
|---|---|
| TypeScript Codegen | Run npx nodepress generate:types to generate 100% type-safe TypeScript interfaces |
@nodepress-cms/client | Typed, dependency-free JS/TS client — lists, entries, GraphQL, forms, image URLs, live preview |
| Starter sites | Next.js, Astro and Nuxt starters in the NodePress repo |
| OpenAPI 3.1 | /api/openapi.json describes your own content types — generate clients with any OpenAPI tool |
| Schema as code | schema:pull / schema:diff / schema:push keep the content model in git and check it in CI |
| MCP server | Let AI assistants read your content and create drafts (create-nodepress-app mcp) |
| Real-time WebSocket | Socket.io gateway — subscribe to entry:created, entry:updated, media:uploaded, and more |
| Collaborative Presence & Locking | Live user presence bubbles & 60s renewable soft-locks preventing concurrent edit collisions |
| Plugin Hook & Filter Bus | PluginHookBus with priority lifecycle actions (beforeCreate, afterUpdate) and data filters |
| Plugins from the admin | Upload signed plugin packages, or browse and install from the plugin directory |
| API keys | Scoped keys with per-key rate limiting (read / write / all · per content type) |
| Webhooks | HTTP callbacks on any event — HMAC signed, with retry + exponential backoff |
| Swagger UI | Interactive API docs at /api/docs — test every endpoint in the browser |
| GraphQL Playground | Apollo Sandbox at /graphql — explore your full schema and run mutations interactively |
| Feature | What it does |
|---|---|
| Dynamic Image Resizing | On-the-fly Sharp transformations: GET /api/media/:file/transform?w=800&h=600&q=80&format=webp |
| Focal points & smart formats | Set a focal point per image for fit=cover crops; format=auto serves AVIF/WebP by browser |
| Persistent Variant Cache | Automatically saves resized WebP/Avif variants to disk for instant subsequent serving |
| File uploads | Images, PDFs, videos — up to 10MB per file with magic bytes validation |
| Folder organisation | Group media into folders in the admin panel |
| Storage options | Switch from local disk to any S3-compatible bucket (AWS, DigitalOcean Spaces, MinIO), Cloudflare R2 or ImageKit, from the admin or with env vars |
| Feature | What it does |
|---|---|
| Two-Factor Auth (2FA / TOTP) | Built-in RFC 6238 TOTP (Google Authenticator, Authy, 1Password) + 8 backup recovery codes |
| Emergency Admin Reset CLI | npm run reset-admin <email> <pass> instant password & 2FA recovery from terminal |
| Forgot & Reset Password | Secure 15-minute token flow with SMTP support & instant dev-mode link preview |
| Field-Level Access (RBAC) | Restrict read and write access per field to specific roles |
| Roles | Admin · Editor · Contributor · Viewer, plus your own custom roles with per-content-type permissions |
| Single sign-on | Any OpenID Connect provider (Google Workspace, Entra ID, Okta, Auth0, Keycloak) — free |
| Per-content-type overrides | Give Editor full access to blog but read-only on legal |
| JWT + refresh tokens | 7-day access tokens, 30-day silent refresh (HttpOnly cookie with rotation) |
| Helmet + CORS | Security headers on every response, strict multi-origin enforcement |
| Rate limiting | Per-IP throttling on all endpoints, stricter on auth routes |
| Feature | What it does |
|---|---|
| Form builder | Create forms in the UI, get a hosted submission endpoint |
| Email actions | Send submission data to any email address |
| Webhook actions | Forward submissions to Zapier, Make, or your own endpoint |
| 3-layer spam protection | Rate limiting + honeypot field + captcha (Turnstile / hCaptcha / reCAPTCHA) |
| Feature | What it does |
|---|---|
| PostgreSQL Full-Text Search | Indexed GIN full-text search with database-pushed LIMIT / OFFSET pagination |
| Docker Compose stack | One command deploys the full stack — backend, frontend, Nginx, Redis, PgBouncer |
| PgBouncer | Connection pooling — handles traffic spikes without exhausting PostgreSQL |
| Redis | Query-result cache + Socket.io multi-instance sync |
| Prometheus + Grafana | Pre-built dashboard, alerting rules for high error rate, high latency, backend-down |
| Automated daily backups | pg_dump every night at 2am UTC, 30-day retention, optional S3 upload |
| Backup & restore commands | npm run backup / npm run restore — database + uploads in one archive, guarded restore (confirmation, overwrite and version checks, single transaction) |
| Graceful shutdown | SIGTERM / SIGINT handled — no dropped requests on deploy |
| Feature | NodePress | Strapi v4/v5 | WordPress Headless | Payload CMS | Directus |
|---|---|---|---|---|---|
| Price | Free forever | Free / $499+/mo | Free / Plugin fees | Free / Enterprise | Free / $99+/mo |
| Self-hosted | ✅ | ✅ | ✅ | ✅ | ✅ |
| Setup time | ~10 min | 2–4 hrs | 1–2 hrs | 30 min | 30 min |
| Advanced Nested Filters | ✅ where[x][op] | ✅ | 🟡 Plugin req. | ✅ | ✅ |
| TypeScript Codegen | ✅ Built-in CLI | 🟡 Manual | ❌ | ✅ | 🟡 Export |
| Next.js Live Draft Mode | ✅ Integrated | 🟡 Plugin | 🟡 Plugin | ✅ | 🟡 Preview URL |
| Field-Level Security | ✅ Built-in | ✅ Enterprise | 🟡 Plugin | ✅ Hooks | ✅ Permissions |
| Dynamic Image Resizing | ✅ On-the-fly WebP | 🟡 Plugin/CDN | 🔴 Static only | 🟡 Config | ✅ Asset API |
| Plugin / Hook Bus | ✅ Actions & Filters | ✅ Lifecycles |
Where others are ahead: NodePress is new — the plugin ecosystem, community and hosted cloud of Strapi, WordPress, Payload and Directus are far larger, and NodePress runs on PostgreSQL only.
You need three things. Already have them? Jump to Quick Start.
1. Node.js 18+
node -v # must show v18 or higher
Download from nodejs.org — install the LTS version. npm 12 (bundled with newer Node releases) needs Node.js 22.22+, 24.15+ or 26+; NodePress installs cleanly with it.
2. Git
git --version # any version is fine
Download from git-scm.com.
3. PostgreSQL 14+ (or use Docker — see Option A below)
psql --version # must show 14 or higher
Download from postgresql.org.
Pass --docker to include the Docker setup — it manages PostgreSQL and Redis for you.
npx create-nodepress-app my-project --docker
cd my-project
docker-compose up -d # starts PostgreSQL + Redis
npm run migrate # creates all DB tables
npm run dev # backend :3000 + admin panel :5173 (one command)
npx create-nodepress-app my-project
cd my-project
Open backend/.env and set your PostgreSQL credentials:
DATABASE_URL="postgresql://postgres:YOUR_PASSWORD@localhost:5432/nodepress"
Then, from the project root:
npm run migrate # creates all DB tables
npm run dev # backend :3000 + admin panel :5173 (one command)
Works with no code changes: prisma/schema.prisma already declares both a pooled url and a directUrl, which is exactly the split Supabase needs.
In your Supabase project open Connect. You'll see several connection strings — which one goes where matters:
| Supabase mode | Port | Goes in | Used for |
|---|---|---|---|
| Transaction pooler | 6543 | DATABASE_URL | Runtime queries (PgBouncer-pooled) |
| Session pooler | 5432 | DIRECT_URL | Migrations (DDL can't run through a transaction pooler) |
DATABASE_URL="postgresql://postgres.<ref>:<PASSWORD>@aws-0-<region>.pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1"
DIRECT_URL="postgresql://postgres.<ref>:<PASSWORD>@aws-0-<region>.pooler.supabase.com:5432/postgres"
Then create the tables — note this is not npm run migrate:
cd backend && npx prisma migrate deploy
Three things that will bite you otherwise:
db.<ref>.supabase.co). It is IPv6-only unless you've bought the IPv4 add-on, so on most home and office networks it fails with P1001: Can't reach database server — which reads like Supabase is down when it's actually unroutable from your machine. Use the Session pooler for DIRECT_URL instead; it's the same database over IPv4. To check: dig +short A db.<ref>.supabase.co returning nothing means IPv6-only.?pgbouncer=true&connection_limit=1 on DATABASE_URL. Without it Prisma eventually throws prepared statement "s0" already exists, and only under concurrency — so it looks intermittent and unrelated to configuration.npx prisma migrate deploy, not npm run migrate. That script runs prisma migrate dev, which wants a shadow database and CREATE DATABASE rights the pooler user doesn't have. migrate dev is for authoring new migrations locally; migrate deploy applies existing ones.If your password contains characters like @, : or /, percent-encode them (@ becomes %40) or the URL won't parse.
Everything else — JWT_SECRET, CORS_ORIGIN, PORT — is unchanged. Since there's no local database daemon, nothing needs starting after a reboot.
| Flag | Effect |
|---|---|
--docker | Include the Docker setup (docker-compose*.yml, nginx/, caddy/, monitoring/, docker:* scripts). Manages PostgreSQL + Redis for you. |
--sentry | Include Sentry error tracking (@sentry/* packages, ~100 MB). Off by default — without it the scaffold is lighter and ships no error-tracking code. Enable later by re-scaffolding with --sentry, or set SENTRY_DSN / NEXT_PUBLIC_SENTRY_DSN once the packages are installed. |
npx create-nodepress-app my-project --docker --sentry # both
git clone https://github.com/buildwithkode/nodepress.git
cd nodepress
# Configure environment
cp backend/.env.example backend/.env
# Edit backend/.env — set DATABASE_URL, JWT_SECRET, CORS_ORIGIN
cp frontend/.env.local.example frontend/.env.local
# Install everything (npm workspaces — one install for backend + frontend)
npm install
# Create DB tables, then start backend (:3000) + admin panel (:5173) together
npm run migrate
npm run dev
Open http://localhost:5173 — you'll see the NodePress setup screen.
Enter your site name, email, and password. Save these credentials — password reset requires SMTP to be configured. If you lose access before setting up SMTP, run npx prisma studio in the backend/ folder to reset directly.
Any Ubuntu 22.04 VPS works. Recommended providers:
| Provider | Spec | Price |
|---|---|---|
| Hetzner | 2 vCPU · 4GB RAM | ~€6/mo |
| DigitalOcean | 2 vCPU · 2GB RAM | ~$12/mo |
| Vultr | 1 vCPU · 2GB RAM | ~$6/mo |
Minimum: 1 vCPU · 1GB RAM. Recommended: 2 vCPU · 2GB RAM for Grafana + Redis.
curl -fsSL https://get.docker.com | sh
git clone https://github.com/buildwithkode/nodepress.git
cd nodepress
Create your production environment file:
# backend/.env
NODE_ENV=production
DATABASE_URL=postgresql://postgres:your_password@localhost:5432/YOUR_NODEPRESS_DATABASE
DIRECT_URL=postgresql://postgres:STRONG_PASSWORD@db:5432/YOUR_NODEPRESS_DATABASE
DB_PASSWORD=STRONG_PASSWORD
JWT_SECRET=<run: openssl rand -hex 32>
CORS_ORIGIN=https://yourdomain.com
APP_URL=https://yourdomain.com
SITE_URL=https://yourdomain.com
# SMTP — required for password reset (see providers below)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
[email protected]
SMTP_PASS=xxxx xxxx xxxx xxxx
[email protected]
# Protect the metrics endpoint
METRICS_TOKEN=<run: openssl rand -hex 16>
Open nginx/default.conf and replace YOUR_DOMAIN with your actual domain:
server_name yourdomain.com www.yourdomain.com;
NODE_ENV=production docker-compose -f docker-compose.prod.yml up -d --build
This starts: PostgreSQL · PgBouncer · Redis · NestJS backend · Next.js frontend · Nginx · Prometheus · Grafana · daily backup job.
Migrations run automatically on first start.
apt install certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com -d www.yourdomain.com
Then uncomment the HTTPS server block in nginx/default.conf and reload:
docker-compose -f docker-compose.prod.yml restart nginx
Visit https://yourdomain.com → complete the setup wizard → you're live.
Development: No SMTP needed — the reset link is shown directly in the browser UI. Production: Configure one of the providers below before going live.
Gmail — free, works with any Google account
backend/.env:SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
[email protected]
SMTP_PASS=xxxx xxxx xxxx xxxx
[email protected]
Resend — recommended for developers, 100 free emails/day
backend/.env:SMTP_HOST=smtp.resend.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=resend
SMTP_PASS=re_xxxxxxxxxxxx
[email protected]
SendGrid — 100 free emails/day
backend/.env:SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=SG.xxxxxxxxxxxx
[email protected]
Before going live, verify:
JWT_SECRET is at least 32 random characters (openssl rand -hex 32)NODE_ENV=production is setCORS_ORIGIN matches your exact frontend domain — no trailing slashMETRICS_TOKEN is set — otherwise /api/metrics is publicly readableNo! For consumer websites and visitor frontends (Next.js, React, Astro, Vue, Svelte, or mobile apps), content reading and form submissions require ZERO authentication tokens. You can query published content directly from client-side or server-side code without passing headers or secret keys.
| Tier | Endpoints | Auth Header Needed? | Typical Frontend Use Case |
|---|---|---|---|
| Tier 1: Public Read APIs | GET /api/:contentTypeGET /api/:contentType/:slugGET /api/brandPOST /api/submit/:formSlugGET /api/sitemap.xml | None (Zero Token) | Visitor frontends, blog articles, product catalogs, contact forms, SEO sitemaps |
| Tier 2: Headless Frontends & SSG | All endpoints (scoped by key permissions) | X-API-Key: np_live_... | Jamstack static site builds (Astro, Next.js SSG), CI/CD content sync pipelines, external microservices |
| Tier 3: Admin & Content Mutations | POST/PUT/DELETE /api/entriesPOST /api/media/upload/api/users | Authorization: Bearer <JWT> | Admin dashboards, content editing, schema builder, media asset manager |
// Fetch published articles — zero headers, zero token required!
const res = await fetch('http://localhost:3001/api/posts?status=published');
const { data: posts, meta } = await res.json();
console.log(`Fetched ${posts.length} posts (Total: ${meta.total})`);
@/lib/nodepress)import { fetchEntries, fetchEntry } from '@/lib/nodepress';
// List entries with automatic pagination & sorting
const { data: articles } = await fetchEntries({
type: 'blog',
page: 1,
limit: 10,
sort: 'createdAt:desc',
});
// Fetch a single entry by slug (for dynamic [slug] routes)
const post = await fetchEntry({
type: 'blog',
slug: 'hello-world',
});
// SSG build or external microservice
const res = await fetch('http://localhost:3001/api/entries?contentTypeId=articles', {
headers: {
'X-API-Key': process.env.NODEPRESS_API_KEY, // e.g. np_live_...
},
next: { revalidate: 3600 },
});
const entries = await res.json();
The GraphQL endpoint is at /graphql. Apollo Sandbox (interactive playground) is available in all environments — click GraphQL Playground in the Developer sidebar.
All write mutations and protected queries require a JWT Bearer token. Three ways to get one:
Option A — Login API (curl):
curl -X POST http://localhost:3001/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"yourpassword"}'
# Response: { "access_token": "eyJhbGci..." }
Option B — Browser cookie: Log into the admin panel → open DevTools (F12) → Application → Cookies → copy the value of np_token. That is your Bearer token (valid 7 days).
Option C — API key: Create one in Admin → API Keys. Use X-API-Key: np_yourkey header instead of Bearer token (no expiry).
Use the token in any request:
Authorization: Bearer eyJhbGci...
In Apollo Sandbox: click Headers tab at the bottom → add { "Authorization": "Bearer eyJhbGci..." }.
query { entries(contentTypeId: 1, status: "published") { total data { id slug data } } }
query { entry(id: 1) { id slug status data createdAt } }
query { contentTypes { id name schema } }
query { contentType(id: 1) { id name schema } }
query { mediaFiles(page: 1, limit: 20) { total data { id filename url mimetype } } }
query { webhooks { id name url events enabled } } # admin only
query { webhookDeliveries(webhookId: 1) { id status attempts responseStatus } }
mutation { createEntry(contentTypeId: 1, slug: "hello", locale: "en", status: "draft", data: "{\"title\":\"Hello\"}") { id slug } }
mutation { updateEntry(id: 1, status: "published", data: "{\"title\":\"Updated\"}") { id updatedAt } }
mutation { deleteEntry(id: 1) { message } } # soft delete
mutation { restoreEntry(id: 1) { id slug } }
mutation { purgeEntry(id: 1) { message } } # permanent, admin only
mutation { restoreEntryVersion(entryId: 1, versionId: 3) { id updatedAt } }
mutation { bulkPublishEntries(ids: [1,2,3]) { affected } }
mutation { bulkArchiveEntries(ids: [4,5]) { affected } }
mutation { bulkDeleteEntries(ids: [6,7]) { affected } }
mutation { bulkSetPendingReviewEntries(ids: [8,9]) { affected } }
mutation { createContentType(name: "blog", schema: "[{\"name\":\"title\",\"type\":\"text\"}]") { id name } }
mutation { updateContentType(id: 1, schema: "[{\"name\":\"title\",\"type\":\"text\"},{\"name\":\"body\",\"type\":\"richtext\"}]") { id } }
mutation { deleteContentType(id: 1) { id name } }
mutation { createWebhook(name: "Deploy", url: "https://example.com/hook", events: ["entry.created"]) { id } }
mutation { toggleWebhook(id: 1, enabled: false) { id enabled } }
mutation { pingWebhook(id: 1) { message } }
mutation { deleteWebhook(id: 1) { message } }
Add Authorization: Bearer YOUR_JWT_TOKEN header for authenticated mutations. Public queries (entries, contentTypes) work without auth but return published entries only.
A project made with create-nodepress-app starts on a release and records it in nodepress.json. To move it to a newer release, commit your work, then:
npx create-nodepress-app upgrade --dry-run # what would change (default target: the latest release)
npx create-nodepress-app upgrade # or: upgrade 1.9.0
# fix any conflicts it lists, then:
npm install # workspaces — backend + frontend
cd backend && npx prisma migrate deploy && cd ..
# restart, check the admin, commit
The upgrade applies the changes between your release and the new one to your code, keeping your own edits; where you and NodePress changed the same lines it leaves conflict markers. It skips files your project doesn't have (tests, the CLI, files you deleted) and lists them. Your project's root package.json is its own, so when NodePress's changes, the command shows how to compare them. Back up the database before migrate deploy. Prisma applies only the new migrations, so skipping releases is fine, e.g. 1.7.0 straight to 1.9.2.
Projects created before nodepress.json existed say once which release they started from: npx create-nodepress-app upgrade --from 1.7.0 (backend/package.json has the version). If you started from an unreleased main, use the release before it; the extra changes show up as conflicts you can resolve.
See CHANGELOG.md for what changed in each version, including upgrade notes for major versions.
| Layer | Technology |
|---|---|
| Backend | NestJS · TypeScript · Prisma ORM |
| Database | PostgreSQL 16 · PgBouncer |
| Cache | Redis (ioredis) |
| Auth | JWT · Passport · bcryptjs |
| API | REST · Apollo GraphQL · Swagger |
| Real-time | Socket.io (Redis adapter for multi-instance) |
| Media | Multer · Sharp (WebP) · S3 compatible · R2 · ImageKit |
| Frontend | Next.js 14 App Router · TypeScript · Tailwind CSS |
| Proxy | Nginx (gzip · rate limiting · WebSocket · SSL) |
| Monitoring | Prometheus · Grafana · pino structured logging |
| Testing | Jest · Supertest · 173 e2e tests |
"Cannot connect to the server" on login
The backend isn't running. In the backend/ folder run npm run start:dev, then check http://localhost:3001/api/health (or http://localhost:3000/api/health depending on your PORT config).
AggregateError [ECONNREFUSED] ... port 3001 (or port 3000), or the login page just fails
PostgreSQL isn't running, so the backend exited during startup while the frontend kept going. The error names the frontend's proxy port and never mentions the database, which makes it look like a broken login page. npm run dev checks for this before starting and tells you which command to run; set SKIP_DB_CHECK=1 to bypass it.
PostgreSQL doesn't start automatically after a reboot (macOS Ventura or newer)
macOS Background Task Management blocks launch daemons independently of launchctl, so the daemon can be enabled, have a valid plist and RunAtLoad, and still never start at boot. launchctl bootstrap appears to fix it because a manual start isn't gated — but the next reboot leaves the database down again.
Confirm it with:
log show --last 1h --predicate 'eventMessage CONTAINS "postgresql"' | grep disposition
disposition=[enabled, disallowed, ...] means Background Task Management is blocking it. launchctl cannot override this — fix it in System Settings → General → Login Items & Extensions → Allow in the Background, and enable the postgres entry (it points at /Library/LaunchDaemons/postgresql-*.plist). Then reboot and confirm with lsof -nP -iTCP:5432 -sTCP:LISTEN.
Login fails with correct credentials
You may have hit the rate limit (10 attempts/min in production). Restart the backend to clear it. If the problem persists, check http://localhost:3001/api/auth/setup-status — if required: true, setup wasn't completed.
"Your session expired" after signing in Normal behaviour after 30 days of inactivity (refresh token lifetime). Just sign in again — no data is lost.
Forgot password — no email received
In development: the reset link appears directly on the forgot-password page — no SMTP needed.
In production: configure SMTP_HOST, SMTP_USER, SMTP_PASS, and SMTP_FROM in backend/.env using one of the providers above.
npx prisma migrate dev fails with auth error
The password in DATABASE_URL doesn't match your PostgreSQL installation. Update backend/.env:
DATABASE_URL="postgresql://postgres:YOUR_POSTGRES_PASSWORD@localhost:5432/YOUR_NODEPRESS_DATABASE"
Admin panel shows blank page or 401
The frontend can't reach the backend. Confirm npm run start:dev is running in backend/ and that CORS_ORIGIN=http://localhost:5173 is set in backend/.env.
Images not loading after deploy
Set APP_URL in backend/.env to your backend's public URL (e.g. https://yourdomain.com). Without it, uploaded file URLs point to localhost.
Sitemap shows localhost URLs
Set SITE_URL in backend/.env to your frontend's public URL (e.g. https://yourdomain.com).
Port 3000 or 5173 already in use
Change PORT=3001 in backend/.env and update CORS_ORIGIN and BACKEND_URL accordingly.
Real-time updates not working in production
Ensure your nginx config includes the /api/realtime WebSocket location block (included by default in nginx/proxy.conf). Check that port 80/443 is open and nginx is running.
GraphQL Playground shows a blank page
Browser cached an old response with stale security headers. Open an Incognito window (works immediately) or press Ctrl+Shift+Delete → clear Cached images and files → refresh. This only happens once after a server restart where headers changed.
NodePress is open source and contributions are welcome.
/docs in your running admin panel1-click deploy at nodepress.buildwithkode.com — $?/mo per project. No Docker, no server management, automated backups included.
Copyright © 2026-present BuildWithKode.
NodePress is open source software licensed under the MIT License. Free to use, modify, and distribute — forever.
| Define a field set once (SEO, address, CTA) and use it in any content type |
| Two-way relations | Reverse fields list what links to an entry; filter by a related entry's fields |
| Content releases | Publish, unpublish or archive many entries together, now or on a schedule |
| Live preview | Edit an entry and watch your site update beside it; click an element to jump to its field |
| Redirects | 301/302/410 manager with CSV import, plus automatic redirects when a slug changes |
| Audit log | Every admin action is logged with user, IP, resource, and timestamp |
| ✅ Hooks |
| ✅ Hooks |
| ✅ Flows |
| Real-time WebSocket | ✅ Built-in | ❌ Paid | ❌ | ❌ | ✅ |
| Audit log | ✅ Built-in | ❌ Paid | 🟡 Plugin | ✅ | ✅ |
| Automated backups | ✅ Built-in | ❌ | 🟡 Plugin | ❌ | ❌ |
| Version history | ✅ | ✅ Paid | ✅ | ✅ | ✅ |
| Content releases | ✅ Free | ✅ Paid | ❌ | ❌ | ❌ |
| Review workflow stages | ✅ Free | ✅ Paid | 🟡 Plugin | 🟡 Build it | ✅ Flows |
| Single sign-on (OIDC) | ✅ Free | ✅ Paid | 🟡 Plugin | 🟡 Build it | ✅ |
| Custom roles | ✅ | ✅ | ✅ | ✅ Access functions | ✅ |
| TypeScript end-to-end | ✅ | ✅ | N/A | ✅ | ✅ |