ECS Full Stack
A full-stack example project that integrates a React frontend, an Express backend, and a Discord bot. The app provides a dashboard for server admins to manage bot settings and invites, plus Discord moderation/integration features via a bot running with discord.js.
This README documents how to get the project running, what environment variables are required, where to get Discord keys, how the invite token flow works, and basic troubleshooting tips.
Note: The backend has been updated to support Postgres persistence (see CHANGELOG.md). The backend now requires DATABASE_URL to run in the default configuration; if you prefer the legacy encrypted file store, see the notes under "Developer notes".
Repository layout
frontend/— React (Create React App) frontend. UsesREACT_APP_API_BASEto communicate with the backend in dev and production.backend/— Express backend and API server that also coordinates with thediscord-botlibrary to manage guilds, invites, and settings. Uses environment variables for configuration.discord-bot/— small wrapper that logs the bot in and exposes the discord.js client used by the backend.checklist.md,README.md, other docs and small scripts at repo root.
What this project does
- Provides a React dashboard where a user can view servers the bot is connected to and manage per-server settings (welcome/leave messages, autorole, toggling commands, invite creation/listing/deletion).
- Runs a Discord bot (discord.js) that performs moderation and server features. The backend and bot are closely integrated: the backend hosts the API and the bot client is shared to fetch guild data and manipulate invites/channels/roles.
- Uses a short-lived token flow to authorize invite deletions from the frontend without embedding long-lived secrets in the client.
Expanded: what this app does
- Hosts a dashboard (React) that lists Discord guilds where the bot is present and lets server admins:
- create and manage invites (create invites with options, view persisted invites, copy and revoke)
- configure Welcome and Leave messages and channels
- enable/disable bot commands per server
- set autorole behavior for new members
- Provides a backend API (Express) that coordinates with a discord.js bot to perform live guild operations (fetch channels/roles, create invites, leave guilds)
- Stores configuration and invites in Postgres (recommended) or a legacy encrypted
db.json
Quickstart — prerequisites
- Node.js (recommended 18.x or later) and npm
- A Discord application with a Bot user (to get
DISCORD_CLIENT_IDandDISCORD_CLIENT_SECRET) — see below for setup steps - Optional: a VPS or Tailscale IP if you want to run the frontend/backend on a non-localhost address
Environment configuration (.env)
There are env files used by the backend and frontend. Create .env files in the backend/ and frontend/ folders for local development. Examples follow.
backend/.env (example)
PORT=3002 HOST=0.0.0.0 BACKEND_BASE=http://your-server-or-ip:3002 FRONTEND_BASE=http://your-server-or-ip:3001 CORS_ORIGIN=http://your-server-or-ip:3001 DISCORD_CLIENT_ID=your_discord_client_id DISCORD_CLIENT_SECRET=your_discord_client_secret ENCRYPTION_KEY=a-32-byte-or-longer-secret INVITE_TOKEN_SECRET=optional-second-secret-for-invite-tokens
Postgres example (optional but recommended)
DATABASE_URL=postgres://dbuser:dbpass@100.111.50.59:5432/ecs_db
PORT/HOST: where the backend listens.BACKEND_BASEandFRONTEND_BASE: used for constructing OAuth redirect URIs and links.CORS_ORIGIN: optional; set to your frontend origin to restrict CORS.DISCORD_CLIENT_ID/DISCORD_CLIENT_SECRET: from the Discord Developer Portal (see below).ENCRYPTION_KEYorINVITE_TOKEN_SECRET: used to sign short-lived invite tokens. Keep this secret.
Note: This project previously supported an INVITE_API_KEY static secret; that requirement has been removed. Invite deletes are authorized via short-lived invite tokens by default.
Twitch Live Notifications (optional)
This project can detect when watched Twitch users go live and post notifications to a configured Discord channel for each guild. To enable this feature, add the following to backend/.env:
TWITCH_CLIENT_ID— your Twitch app client idTWITCH_CLIENT_SECRET— your Twitch app client secretTWITCH_POLL_INTERVAL_MS— optional, poll interval in milliseconds (default 30000)
When configured, the backend exposes:
- GET /api/twitch/streams?users=user1,user2 — returns stream info for the listed usernames (used by the frontend and bot watcher)
The bot includes a watcher that polls watched usernames per-guild and posts a message to the configured channel when a streamer goes live. The message includes the stream title and a link to the Twitch stream.
If you run the backend and the bot on separate hosts, you can configure the backend to push setting updates to the bot so toggles and watched users propagate immediately:
BOT_PUSH_URL— the URL the bot will expose for the backend to POST setting updates to (e.g., http://bot-host:4002)BOT_SECRET— a shared secret used by the backend and bot to secure push requestsBOT_PUSH_PORT— optional, the port the bot listens on for push requests (if set the bot starts a small HTTP receiver)
frontend/.env (example)
HOST=0.0.0.0 PORT=3001 REACT_APP_API_BASE=http://your-server-or-ip:3002
Set REACT_APP_API_BASE to point at the backend so the frontend can call API endpoints.
Create a Discord Application and Bot (short)
- Go to the Discord Developer Portal: https://discord.com/developers/applications
- Create a new application.
- Under "OAuth2" -> "General", add your redirect URI:
- For dev:
http://your-server-or-ip:3002/auth/discord/callback - Make sure
BACKEND_BASEmatches the host/port you set inbackend/.env.
- For dev:
- Under "Bot" create a Bot user and copy the Bot token (NOT committed to source).
- Under "OAuth2" -> "URL Generator" select scopes
botandapplications.commandsand select permissions (e.g., Administrator if you want full access for testing). Use the generated URL to invite the bot to your guild during testing.
Store the Client ID / Client Secret in your backend/.env as DISCORD_CLIENT_ID and DISCORD_CLIENT_SECRET.
Invite token flow (why and how)
- To avoid embedding long-lived secrets in a web client, invite deletions are authorized with a short-lived HMAC-signed token.
- The frontend requests a token with: GET /api/servers/:guildId/invite-token
- The backend returns
{ token: '...' }. The frontend then calls DELETE /api/servers/:guildId/invites/:code with headerx-invite-token: <token> - Token TTL is short (default 5 minutes) and is signed using
INVITE_TOKEN_SECRETorENCRYPTION_KEYfrom backend.env.
Security note: Currently the /invite-token endpoint issues tokens to any caller. For production you should restrict this endpoint by requiring OAuth authentication and checking that the requesting user is authorized for the target guild.
Run the app locally
- Backend
cd backend
npm install
# create backend/.env from the example above
npm start
Optional: using Postgres (recommended)
- Create a Postgres database and user (pgAdmin or psql)
- Set
DATABASE_URLinbackend/.env, e.g.: DATABASE_URL=postgres://dbuser:dbpass@100.111.50.59:5432/ecs_db - Start the backend; on startup the backend will create simple tables if missing.
Migration note:
- If you have existing data in
backend/db.json, a migration script is planned to import invites and server settings into Postgres. I can add that script on request.
- Frontend
cd frontend
npm install
# create frontend/.env with REACT_APP_API_BASE pointing to the backend
npm run start
- Discord bot
- The backend boots the bot client (see
discord-bot/), so if the backend is started and credentials are correct, the bot will log in and register slash commands. You can also run thediscord-botproject separately if you prefer.
Troubleshooting
- Backend refuses to start or missing package.json: ensure you run
npm installin thebackendfolder and runnpm startfrom that folder.- If the backend exits with "DATABASE_URL is not set": either set
DATABASE_URLinbackend/.envpointing to your Postgres DB, or restore the legacy behavior by editingbackend/index.jsto re-enable the encrypteddb.jsonfallback (not recommended for production).
- If the backend exits with "DATABASE_URL is not set": either set
- CORS errors: verify
CORS_ORIGINandREACT_APP_API_BASEmatch your frontend origin. - Invite delete unauthorized: ensure backend
INVITE_TOKEN_SECRETorENCRYPTION_KEYis present and token TTL has not expired. Check the backend logs for validation details. - Token issues: clock skew can cause tokens to appear expired — ensure server and client clocks are reasonably in sync.
Developer notes
- The dashboard UI is in
frontend/src/components/(notable files:Dashboard.js,ServerSettings.js,Login.js). - The Express API is in
backend/index.jsand usesdiscord-bot(discord.js client) to operate on guilds, invites, channels and roles. - Invite delete flow: frontend fetches a short-lived token then requests DELETE with header
x-invite-token.
Next steps / suggestions
- Harden
/api/servers/:guildId/invite-tokento require an authenticated user and verify the user has admin permissions for the guild. - Add rate-limiting to token issuance and optionally keep the old
INVITE_API_KEYoption for server-to-server automation. - Updated docs: the README and CHANGELOG were updated to reflect Postgres integration and recent frontend/backend changes. See
CHANGELOG.mdandchecklist.mdfor details.
If you want, I can add step-by-step instructions to create the .env files from templates, or implement the production safe option of authenticating /invite-token requests. Tell me which you'd prefer.
Updated: Oct 4, 2025
Full setup guide (detailed)
This section walks through the exact steps to get the project running locally or on a machine reachable via Tailscale/Nginx Proxy Manager.
Prerequisites
- Node.js 18+ and npm
- Postgres (local or remote) or use an existing Postgres server reachable over your network/Tailscale
- Discord application + Bot credentials and (optional) Twitch app credentials
Database (Postgres) setup
- Create a Postgres database and user. Example psql commands:
sudo -u postgres psql
CREATE DATABASE ecs_fullstack;
CREATE USER ecs_user WITH PASSWORD 'supersecret';
GRANT ALL PRIVILEGES ON DATABASE ecs_fullstack TO ecs_user;
\q
- Set the
DATABASE_URLinbackend/.env:
DATABASE_URL=postgres://ecs_user:supersecret@127.0.0.1:5432/ecs_fullstack
- Start the backend (it will run migrations / ensure tables at startup):
cd backend
npm install
npm start
Backend configuration (.env)
DATABASE_URL- required for Postgres persistenceDISCORD_CLIENT_ID,DISCORD_CLIENT_SECRET,DISCORD_BOT_TOKEN- from Discord Developer PortalFRONTEND_BASE- public frontend URL (used for OAuth redirect)PORT,HOST- where backend listensCORS_ORIGIN- optional restrict origin to your frontend URLTWITCH_CLIENT_ID,TWITCH_CLIENT_SECRET- optional for Twitch integration
Frontend configuration
- In
frontend/.envset:
REACT_APP_API_BASE=https://your-domain-or-ip:3002
- For development behind an HTTPS domain (Nginx Proxy Manager), ensure the CRA dev client uses
wssby setting theWDS_SOCKET_*variables infrontend/.env(see docs if using a TLS domain)
Start the frontend dev server:
cd frontend
npm install
npm start
Bot behaviour and deployment
- The
backendprocess will boot the Discord bot client when validDISCORD_BOT_TOKENis present. The bot registers slash commands per guild on startup and responds to backend pushes for setting updates. - If you prefer to run the bot separately, you can run the
discord-botmodule separately; ensureBOT_PUSH_URL/BOT_SECRETare configured if backend and bot are on different hosts.
Useful endpoints
GET /api/servers/:guildId/commands— returns the authoritative list of commands and per-guild enabled/locked status.GET/POST /api/servers/:guildId/live-notifications— get/update live notification settingsGET /api/twitch/streams?users=user1,user2— proxy to twitch helix for streams (backend caches app-token)GET /api/events?guildId=...— Server-Sent Events for real-time updates (ServerSettings subscribes to this)
Notes about Postgres requirement
- The backend now assumes Postgres persistence (via
DATABASE_URL). IfDATABASE_URLis not set the server will exit and complain. This change makes server settings authoritative and persistent across restarts.
Logs and verbosity
- The bot and watcher log messages have been reduced to avoid per-guild spam. You will see concise messages like "🔁 TwitchWatcher started" and "✅ ECS - Full Stack Bot Online!" rather than one-line-per-guild spam.
Troubleshooting
- If you see mixed-content errors in the browser when using a TLS domain with the CRA dev server, configure Nginx to proxy websockets and set CRA
WDS_SOCKET_*env vars (see docs/nginx-proxy-manager.md)