React Redux PostgreSQL
Full-stack template: React (Next.js) + Redux Toolkit on the frontend, Express + PostgreSQL on the backend. Both client and server are written in TypeScript.
Structure
client/— Next.js app (App Router, TypeScript, Redux Toolkit, React-Redux). Static export inclient/out; Express serves it in production.server/— Express API and PostgreSQL viapg(TypeScript). Source inserver/src/; compile withnpm run buildinserver/(output inserver/dist/). Table schemas inserver/src/schema/(e.g.items/index.ts,helpers.ts).scripts/— Installation scripts for Node, npm, and PostgreSQLe2e/— Playwright end-to-end tests
TypeScript
- Client:
client/tsconfig.json(path alias@/*→client/*). A roottsconfig.jsonextends it so editors at repo root get JSX and paths. - Server:
server/tsconfig.json; compiles toserver/dist/. Dev runs withtsx watch; production runsnode dist/index.jsafternpm run build.
Prerequisites: Node, npm, PostgreSQL
Use the scripts in scripts/ to install them on your OS (npm is included with Node):
| OS | Script | How to run |
|----------|---------------------------|------------|
| Windows | scripts/install-windows.ps1 | In PowerShell (Admin optional): .\scripts\install-windows.ps1 |
| Linux | scripts/install-linux.sh | sudo bash scripts/install-linux.sh |
| macOS | scripts/install-mac.sh | bash scripts/install-mac.sh (requires Homebrew) |
After running, restart your terminal and check: node --version, npm --version, psql --version.
Windows: To set a known default password for the postgres user (e.g. postgres/postgres) after installing PostgreSQL, run PowerShell as Administrator: .\scripts\set-postgres-default-password.ps1 (or .\scripts\set-postgres-default-password.ps1 -Password "mypassword").
Setup
1. Install dependencies
npm run install:all
Or install manually:
npm install
cd client && npm install
cd ../server && npm install
2. PostgreSQL
The server creates the database and tables automatically on startup (using PG_DATABASE or the DB name in DATABASE_URL, and DDL from server/src/schema/). No need to run init scripts manually unless you prefer. Use the postgres user and your password. Set a default password on Windows: run as Admin .\scripts\set-postgres-default-password.ps1 for postgres/postgres. Password authentication failed? See scripts/reset-postgres-password-windows.md.
3. Environment
Copy the example env and set your database URL:
cp server/.env.example server/.env
Edit server/.env with either:
- Option A — separate vars (recommended):
PG_USER,PG_PASSWORD,PG_HOST,PG_PORT,PG_DATABASE. The app builds the connection URL from these. - Option B — full URL:
DATABASE_URL=postgresql://user:password@localhost:5432/react_template
Also set PORT=3001 (optional) for the API server.
For auth (signup, login, email verification, password reset), set in server/.env:
JWT_SECRET— Secret for signing JWTs (use a long random string in production).APP_URL— Base URL where users open the app (for verification and reset links). In development usehttp://localhost:5173(Vite). In production use your app URL (e.g.http://localhost:3001if you serve the built app from Express, orhttps://yourdomain.com).- SMTP (optional) — If set, verification and password-reset emails are sent. Set
SMTP_HOST,SMTP_PORT,SMTP_USER,SMTP_PASS, and optionallySMTP_FROM. Without SMTP, signup and login still work; verification emails are skipped.
Auth (signup, email verification, password reset)
- Sign up —
POST /api/auth/signupwith{ email, password }. Creates user and sends verification email (if SMTP is configured). Returns{ user, token }. - Log in —
POST /api/auth/loginwith{ email, password }. Returns{ user, token }. - Me —
GET /api/auth/mewithAuthorization: Bearer <token>returns{ user }or{ user: null }. - Verify email — User clicks link in email;
POST /api/auth/verify-emailwith{ token }(or?token=). Setsemail_verified_aton the user. - Forgot password —
POST /api/auth/forgot-passwordwith{ email }. Sends reset link by email (if SMTP configured). - Reset password — User clicks link;
POST /api/auth/reset-passwordwith{ token, newPassword }.
Frontend routes: /signup, /login, /verify-email?token=, /forgot-password, /reset-password?token=. Token is stored in localStorage and sent as Authorization: Bearer on API requests.
Server schema
Table definitions live under server/src/schema/. Each table has a folder (e.g. items/) with an index.js that defines the table and reuses shared helpers.
Table schema file (e.g. schema/items/index.ts)
tableName— Table name string (e.g.'items').columns— Map of column name → metadata. Column metadata can include:name,type— Column name and SQL type (e.g.'SERIAL','VARCHAR(255)','TIMESTAMPTZ').order— Numeric order for column order in DDL and constraints (default0).isNull,isPrimaryKey,isDefault— Booleans for NOT NULL, PRIMARY KEY, and whether the column has a default.defaultExpr— Optional SQL expression for DEFAULT (e.g.'NOW()').references— Optional{ table, column? }for foreign keys (columndefaults to target primary key).isUnique— Optional; column is a single-column unique constraint.uniqueGroup— Optional; columns with the same string form one composite unique constraint.
primaryKey,foreignKeys,uniqueConstraints— Derived fromcolumnsvia helpers (see below).createTableSql— GeneratedCREATE TABLE IF NOT EXISTS ...fromtableNameandcolumns.seedRows— Optional array of row arrays (one per column value in order) for initial seed when the table is empty.
Schema helpers (schema/helpers.ts)
getOrderedColumnNames(columns)— Column names sorted by each column’sorder.getPrimaryKey(columns)— Primary key column name(s), ordered byorder(single string or array for composite).getForeignKeys(columns)— List of{ column, references }for columns that havereferences.getUniqueConstraints(columns)— List of column-name arrays: single-column fromisUnique, composite from shareduniqueGroup.generateCreateTableSql(tableName, columns)— BuildsCREATE TABLE IF NOT EXISTSwith columns inorder.
The server runs ensureDatabase() then ensureSchema() at startup, which create the DB (if missing) and run each table’s createTableSql and optional seed.
Run
Both client and server:
npm run dev
- Frontend: http://localhost:3000 (Next.js dev server; proxies
/apito the Express server) - API: http://localhost:3001
Only client: npm run dev:client
Only server: npm run dev:server
Unit tests
Uses Vitest and Testing Library in the client:
cd client && npm run test:run
Watch mode: npm run test.
E2E tests
Uses Playwright. Start the app and DB first (or let the runner start them via webServer).
npx playwright install
npm run e2e
With UI: npm run e2e:ui. The runner starts the dev server automatically unless CI is set.
Build & production
npm run build
npm run start
Then open http://localhost:3001 — the Express server serves both the API and the built Next.js app from client/out. If you run npm run start without building first, port 3001 only serves the API (no GET /); use http://localhost:3000 for the app during development (npm run dev).
Hosting (so others can see it)
You need to run one Node app (the server) and give it a PostgreSQL database. The server already serves the built React app from client/dist, so you don’t deploy the client separately.
Option 1: Render (free tier)
- Push your code to GitHub (or GitLab).
- Create a PostgreSQL database
Render Dashboard → New + → PostgreSQL. Note the Internal Database URL (or External if your app is elsewhere). - Create a Web Service
New + → Web Service → connect your repo.- Root Directory: leave blank.
- Build Command:
npm run install:all && npm run build(builds Next.js static export toclient/out) - Start Command:
npm run start - Instance type: Free (or paid).
- Environment variables (in the Web Service → Environment):
NODE_ENV=productionDATABASE_URL= (paste the Postgres URL from step 2; use Internal if app and DB are both on Render)JWT_SECRET= (generate a long random string, e.g.openssl rand -hex 32)APP_URL=https://YOUR-SERVICE-NAME.onrender.com(your Web Service URL; replace with your real URL after first deploy)PORT=3001(or leave unset; Render setsPORTfor you)
- Deploy. After the first deploy, set
APP_URLto the exact URL Render gives you (for auth links).
Option 2: Railway
- Push code to GitHub and open Railway.
- New Project → Deploy from GitHub and select the repo.
- Add PostgreSQL: in the project, New → Database → PostgreSQL. Railway will set
DATABASE_URLautomatically if you add it to the same project. - In your service settings set:
Build:npm run install:all && npm run build
Start:npm run start
Add env:JWT_SECRET,APP_URL=https://your-app.up.railway.app(use the URL Railway gives you). - Deploy; then set
APP_URLto the real public URL.
Option 3: VPS (DigitalOcean, Linode, etc.)
- Create a droplet/VM, install Node and PostgreSQL.
- Clone the repo, run
npm run install:all,npm run build, thennpm run start(e.g. withpm2or systemd). - Set env in
server/.envor systemd:DATABASE_URL,JWT_SECRET,APP_URL,PORT. - Put a reverse proxy (e.g. nginx) in front and use SSL (e.g. Let’s Encrypt).
Checklist for any host
- Build the client before starting:
npm run build(producesclient/out). The server serves it automatically. - APP_URL must be the exact URL people use (e.g.
https://myapp.onrender.com) so verification and reset links work. - JWT_SECRET must be a strong random value in production.
- Database: use the host’s Postgres URL; the server runs migrations (ensureSchema) on startup.
- Optional: set SMTP env vars so verification and password-reset emails are sent.