# Run your side projects locally: PostgreSQL + PM2

A simple, free setup for running multiple apps (web apps + Telegram bots) on your own computer — no cloud bill, no Docker complexity. One database server for everything, and one process manager that keeps your apps alive.

This is the exact setup I use for all my projects during development. **Works on macOS and Windows** (install steps differ; the architecture is the same).

---

## The idea in one picture

```
Your computer (always on)
│
├── PostgreSQL 16  ──►  one server, one database per project
│                       (webapp, shop, bot, ...)
│
└── PM2  ──────────►  keeps every app running in the background
                        (web apps + Telegram bots), restarts on crash,
                        survives closing the terminal
```

- **PostgreSQL** = where your data lives.
- **PM2** = what keeps your apps running.

Two tools, and your whole local stack is online 24/7 on your own machine.

---

## Part 1 — Why local PostgreSQL?

When you're building, you don't need a paid cloud database yet. A single Postgres server on your laptop gives you:

- **Free & fast** — no network latency, no usage limits.
- **One server, many projects** — each project gets its own database, fully isolated, zero conflicts.
- **Real Postgres** — same engine as Supabase/Neon/Railway, so moving to production later is easy.

> **Important limitation:** this database only listens on `localhost`. It is **not** reachable from the internet or from an app deployed on Replit/a VPS. Use it for local development and apps you run on your own machine. For deployed apps that other people use, get a hosted DB (Supabase, Neon, Railway).

### Setup — macOS (Apple Silicon)

```bash
# 1. Install Postgres (Homebrew)
brew install postgresql@16

# 2. Make it always-on (starts on login, restarts on crash)
brew services start postgresql@16

# 3. Put Postgres 16 on your PATH
echo 'export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

# 4. Create one database per project
createdb webapp
createdb shop
createdb bot
```

By default Homebrew's Postgres uses your macOS username with **no password** for local connections — perfect for development.

Handy commands (macOS):

```bash
brew services list        # is Postgres running?
psql -l                   # list all databases
psql webapp               # open a database
createdb my_new_project   # add a database for a new project
```

### Setup — Windows

Same idea: one always-on Postgres service, one database per project.

```powershell
# 1. Install Postgres 16 (pick one)
winget install PostgreSQL.PostgreSQL.16
# or download the installer: https://www.postgresql.org/download/windows/

# During install: set a password for the `postgres` user and keep port 5432.
# Add "Command Line Tools" / bin to PATH when the installer offers it
# (or add C:\Program Files\PostgreSQL\16\bin manually).

# 2. Confirm the Windows service is running
# Services app → "postgresql-x64-16" → Running
# Or in an elevated PowerShell:
Get-Service -Name postgresql*

# 3. Open a new terminal, then create one database per project
createdb -U postgres webapp
createdb -U postgres shop
createdb -U postgres bot
# You'll be prompted for the postgres password you set at install.
```

Handy commands (Windows):

```powershell
psql -U postgres -l              # list databases
psql -U postgres -d webapp       # open a database
createdb -U postgres my_project  # new project DB
```

> **Windows note:** the installer usually creates a `postgres` superuser **with a password** (unlike Homebrew on Mac, which is often passwordless for your OS user). Use that password in `DATABASE_URL`.

### Connect an app

Put this in each project's `.env` (pointing at that project's own database):

**macOS (typical):**
```
DATABASE_URL="postgresql://<your-mac-username>@localhost:5432/webapp"
```

**Windows (typical):**
```
DATABASE_URL="postgresql://postgres:<your-password>@localhost:5432/webapp"
```

That's it — the app talks to its own database on the shared server.

---

## Part 2 — Why PM2?

During development you run apps with `npm run dev`. But that dies the moment you close the terminal. PM2 fixes this:

- **Keeps apps running in the background** — close the terminal, they stay up.
- **Auto-restart on crash** — if a bot throws, PM2 brings it back.
- **Runs many apps at once** — web apps *and* Telegram bots, all managed together.
- **One command to rule them all** — start / reload / stop everything at once.
- **Survives reboot** (once configured) — apps come back after you restart the computer.

### Setup

```bash
# Install PM2 globally
npm install -g pm2
```

Each project describes its processes in an `ecosystem.config.cjs` file. Example (a web app + a Telegram bot):

```javascript
// ecosystem.config.cjs
const path = require("path");

module.exports = {
  apps: [
    {
      name: "myapp-web",
      cwd: __dirname,
      script: "npm",
      args: "run start",       // or "run dev"
      env: { NODE_ENV: "production", PORT: 4783 },
      autorestart: true,
      max_restarts: 10,
    },
    {
      name: "myapp-telegram",
      cwd: __dirname,
      script: path.join(__dirname, "node_modules/.bin/tsx"),
      args: "telegram/bot.ts",
      interpreter: "none",
      autorestart: true,
      max_restarts: 10,
    },
  ],
};
```

### Run one app

```bash
pm2 start ecosystem.config.cjs   # start this project's processes
pm2 reload all                   # reload after code changes
pm2 stop all                     # stop
pm2 list                         # see what's running
pm2 logs                         # tail logs
```

### Run *all* your projects with one command

If you have several projects, add a tiny workspace script so you can control them all at once.

#### macOS / Git Bash / WSL — `scripts/pm2-all.sh`

```bash
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"

ECOSYSTEMS=(
  "$ROOT/webapp/ecosystem.config.cjs"
  "$ROOT/shop/ecosystem.config.cjs"
  "$ROOT/bot/ecosystem.config.cjs"
)

case "${1:-}" in
  start)
    for eco in "${ECOSYSTEMS[@]}"; do pm2 startOrReload "$eco" --update-env; done
    pm2 save && pm2 list ;;
  reload) pm2 reload all --update-env && pm2 list ;;
  stop)   pm2 stop all && pm2 list ;;
  status) pm2 list ;;
  *) echo "Usage: $0 <start|reload|stop|status>"; exit 1 ;;
esac
```

Root `package.json` (macOS / Git Bash):

```json
{
  "scripts": {
    "pm2:start":  "bash scripts/pm2-all.sh start",
    "pm2:reload": "bash scripts/pm2-all.sh reload",
    "pm2:stop":   "bash scripts/pm2-all.sh stop",
    "pm2:status": "bash scripts/pm2-all.sh status"
  }
}
```

#### Windows PowerShell — `scripts/pm2-all.ps1`

```powershell
param(
  [Parameter(Mandatory = $true)]
  [ValidateSet("start", "reload", "stop", "status")]
  [string]$Action
)

$Root = Split-Path -Parent $PSScriptRoot
$Ecosystems = @(
  "$Root\webapp\ecosystem.config.cjs",
  "$Root\shop\ecosystem.config.cjs",
  "$Root\bot\ecosystem.config.cjs"
)

switch ($Action) {
  "start" {
    foreach ($eco in $Ecosystems) { pm2 startOrReload $eco --update-env }
    pm2 save
    pm2 list
  }
  "reload" { pm2 reload all --update-env; pm2 list }
  "stop"   { pm2 stop all; pm2 list }
  "status" { pm2 list }
}
```

Root `package.json` (Windows):

```json
{
  "scripts": {
    "pm2:start":  "powershell -ExecutionPolicy Bypass -File scripts/pm2-all.ps1 start",
    "pm2:reload": "powershell -ExecutionPolicy Bypass -File scripts/pm2-all.ps1 reload",
    "pm2:stop":   "powershell -ExecutionPolicy Bypass -File scripts/pm2-all.ps1 stop",
    "pm2:status": "powershell -ExecutionPolicy Bypass -File scripts/pm2-all.ps1 status"
  }
}
```

Same daily commands on both OSes (from your dev folder):

```bash
pnpm run pm2:start     # start every project
pnpm run pm2:reload    # after you change code
pnpm run pm2:stop      # stop everything
pnpm run pm2:status    # see everything
```

### Survive a reboot (one-time)

```bash
pnpm run pm2:start
pm2 save            # remember the current app list
pm2 startup         # prints an OS-specific command — run it once
```

- **macOS:** `pm2 startup` prints a `sudo ... launchd ...` command — paste and run it.
- **Windows:** `pm2 startup` prints instructions to install a Windows startup/service hook — run what it prints (often needs an **Administrator** terminal). Then `pm2 save` again if asked.

After this: closing the terminal keeps apps running, and a reboot restores them automatically.

### Daily workflow after you change code

```bash
pnpm run pm2:reload
```

> Apps running a **production** build (`next start`) need a rebuild first: `npm run build` in that project, then reload. Apps in **dev** mode reload as-is.

---

## Part 3 — PM2 vs Docker: what's the difference?

Both keep apps running, but they solve different problems.

| | **PM2** | **Docker** |
|---|---|---|
| What it is | A process manager for Node apps | A container platform that packages the whole environment |
| What it manages | Your running Node processes | Isolated containers (OS libs, runtime, app, sometimes the DB too) |
| Setup effort | `npm i -g pm2`, one config file | Dockerfiles, images, volumes, networks, compose files |
| Resource use | Very light | Heavier (each container ships its own environment) |
| Isolation | Shares your machine's Node/OS | Fully isolated per container |
| Best for | Local dev, small always-on apps on one machine | Reproducible deploys, teams, complex multi-service systems, "works on every machine" |
| Learning curve | Minutes | Hours/days |

### Why we chose PM2 (browser-based apps) for these projects

Our projects are **browser-based web apps + Telegram bots** running on **one developer's machine** during development. For that, PM2 is the right tool:

1. **Simplicity** — one config file per app, one command to run them all. No Dockerfiles, images, or volumes to maintain.
2. **Lightweight** — the apps run directly on the computer's Node. No container overhead eating RAM/CPU on a laptop.
3. **Fast iteration** — change code, `pnpm run pm2:reload`, done. No rebuilding images.
4. **Shared local Postgres** — every app just points `DATABASE_URL` at `localhost:5432`. No container networking to wire up.
5. **Good enough uptime** — auto-restart on crash + restore on reboot is exactly what a solo dev needs locally.

**When we'd switch to Docker:** when an app goes to production on a server, needs to run identically across many machines/teammates, or has many interdependent services. Docker shines for *deployment and reproducibility*; PM2 shines for *simple local always-on*. For the build phase, PM2 keeps us fast and cheap.

---

## TL;DR

**macOS**
```bash
brew install postgresql@16 && brew services start postgresql@16
npm install -g pm2
createdb my_project
# DATABASE_URL="postgresql://<mac-username>@localhost:5432/my_project"
```

**Windows**
```powershell
winget install PostgreSQL.PostgreSQL.16
npm install -g pm2
createdb -U postgres my_project
# DATABASE_URL="postgresql://postgres:<password>@localhost:5432/my_project"
```

**Every day (both)**
```bash
pnpm run pm2:start     # bring everything online
pnpm run pm2:reload    # after code changes
pnpm run pm2:status    # check health
```

Free, local, always-on. Move to a hosted DB + Docker only when you actually deploy.
