3.9 KiB
Val-Friendbook
A small self-hosted friendbook for the lovely peeps I meet on there. Everyone can submit their own page (name, fun fact, favorite color, mood, badge images, ...). New entries stay hidden until an admin approves them.
Plain Express + Multer, with a JSON file as the "database". No native modules, no external services.
Features
- Public gallery of approved entries, newest first
- Submission form with up to 5 badge images per entry (PNG, JPG, GIF, WEBP, max. 3 MB each; easily my favourite feature lol)
- Moderation: new entries are
pendinguntil approved in the admin panel - Denying an entry deletes it and its uploaded images
- Admin panel at
/admin, protected by a password
Requirements
- Node.js 18 or newer
Quick start
git clone https://gitea.nkelabs.xyz/nke4/Val-Friendbook.git
cd Val-Friendbook
npm install
ADMIN_PASSWORD='choose-a-strong-password' npm start
The app runs on http://localhost:3030 by default.
Configuration
| Variable | Default | Description |
|---|---|---|
ADMIN_PASSWORD |
changeme |
Password for the admin panel. Always set this. |
PORT |
3030 |
Port the server listens on (the Dockerfile sets 3000). |
If ADMIN_PASSWORD is not set, the server falls back to changeme and prints a warning in the log on startup.
Deployment
pm2
ADMIN_PASSWORD='choose-a-strong-password' PORT=3000 pm2 start server.js --name friendbook
pm2 save
To change the environment of an existing process:
ADMIN_PASSWORD='new-password' pm2 restart friendbook --update-env
pm2 save
Docker
docker build -t val-friendbook .
docker run -d --name val-friendbook \
-p 3000:3000 \
-e ADMIN_PASSWORD='choose-a-strong-password' \
-v "$(pwd)/data:/app/data" \
-v "$(pwd)/uploads:/app/uploads" \
val-friendbook
Data
| Path | Contents |
|---|---|
data/entries.json |
All entries (pending + approved) |
uploads/ |
Uploaded badge images |
Both are created automatically on first start and are excluded from git. Back them up or mount them as volumes so entries survive restarts and redeploys.
Admin panel
Open /admin and log in with ADMIN_PASSWORD. From there you can approve or deny pending entries.
API
| Method | Route | Auth | Description |
|---|---|---|---|
| GET | /api/entries |
- | List approved entries |
| POST | /api/entries |
- | Submit a new entry (multipart, badges) |
| POST | /api/admin/login |
admin | Check the admin password |
| GET | /api/admin/entries |
admin | List all entries |
| POST | /api/admin/entries/:id/approve |
admin | Approve an entry |
| POST | /api/admin/entries/:id/deny |
admin | Delete an entry and its images |
Admin routes expect the password in the x-admin-password header.
Project structure
.
├── server.js # Express backend, JSON storage, admin routes
├── public/ # Frontend (index, admin panel, styles, scripts)
├── Dockerfile
└── package.json
Notes
- The admin password is sent in a header on every admin request. Run the app behind HTTPS (reverse proxy or tunnel) when exposing it publicly.
- Entries are stored in a single JSON file, which is fine for a small friend group but not meant for large scale, any help with this is appreciated, since I can't code for shit