Files
Val-Friendbook/README.md
T
2026-10-23 19:01:14 +02:00

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".

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 pending until 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
  • Docker not fully tested yet, so no guarantee it will work

Enjoy! :3