Lightweight Node.js shopping list app with a small EJS frontend and an API backend. Key design points:
- Session-based authentication (server-side sessions stored in MongoDB via
connect-mongo). - Separate dedicated users database and least-privilege DB users created at MongoDB first-boot.
- Docker Compose stack with optional nginx reverse-proxy for local HTTPS during development.
- Session-based login/logout with a minimal admin bootstrap flow (
scripts/populate-admin.js). - Public read access for the shopping list (
GET /data) and protected modifying endpoints for authenticated users (POST,PATCH,DELETEon/data). - Dedicated users DB (
USER_DB_NAME) and dedicated app DB user for data access. - Minimal EJS-rendered frontend plus an API-first design for fetch/XHR usage.
- Local HTTPS in development via nginx + mkcert (optional) and a Compose-friendly DB bootstrap (entrypoint + one-shot admin populator).
- Tests for controllers (see
tests/).
This app exposes a small set of endpoints for the UI and API clients. All modifying /data requests require an authenticated session; GET /data is public.
| Method | Description |
|---|---|
| GET | Fetch all shopping list items. Responds with a JSON array. (Public) |
| POST | Add a new item. Requires Content-Type: application/json and JSON body: { name, amount, finished }. (Requires session) |
| PATCH | Update an item. Requires Content-Type: application/json and JSON body: { _id, name, amount, finished }. (Requires session) |
| DELETE | Delete an item by _id (query param) or delete all items if _id not provided. (Requires session) |
| Method | Path | Description |
|---|---|---|
| GET | /users/login |
Render the login page (HTML form). |
| POST | /users/login |
Authenticate user. Expects application/x-www-form-urlencoded or JSON { email, password }. On success sets a server-side session. Returns JSON for API clients. |
| POST | /users/logout |
Destroy the current session (log out). |
| GET | /users/me |
Return the currently authenticated user's public info (requires session). |
Notes:
- Unauthenticated modifying requests return HTTP 401 JSON; the frontend handles 401 by redirecting the browser to
/users/login. - The server returns JSON error objects for API clients; HTML form flows receive normal browser navigations.
. ├── bin ├── config ├── controllers ├── docker │ └── nginx │ └── conf.d ├── middleware ├── models ├── public │ └── stylesheets ├── routes ├── scripts ├── services ├── certs/ └── views
Create a .env file in the root directory with the following variables (example values shown):
API_SERVER=http://shoppinglist-app:3000
MONGO_INITDB_ROOT_USERNAME=adminroot
MONGO_INITDB_ROOT_PASSWORD=change_this_root_password
# App DB
MONGODB_DB=shoppinglistdb
MONGO_APP_USER=shoppinglist_app
MONGO_APP_PASSWORD=change_this_app_password
# Collection names
ITEM_COLLECTION_NAME=items
USER_COLLECTION_NAME=users
# Users DB
USER_DB_NAME=usersdb
USER_DB_USER=users_app
USER_DB_PASSWORD=change_this_users_password
# Initial root/admin user for the application
ROOT_EMAIL=[email protected]
ROOT_PASSWORD=change_this_admin_password
# Session secret used by express-session
SESSION_SECRET=change_this_session_secret
#MongoDB connection host
MONGODB_HOST=shoppinglist-mongo- Build and start the containers:
- Visit the app in your browser:
docker compose up --buildhttps://youraddressAll dependencies are installed automatically inside the Docker container, so you don’t need to install anything on the host machine.
This project creates DB-level users during MongoDB first-boot using the mongo-init service (mongosh --eval entry). The application-level admin record is created by a short-lived one-shot service admin_user-populate which runs scripts/populate-admin.js.
How it works:
mongo-initcreates two DB users (app DB and users DB) on first initialization only.admin_user-populateconnects with the dedicatedUSER_DB_USERto insert a hashed admin document into theuserscollection (idempotent upsert).
This repository uses nginx as a local reverse proxy for HTTPS in development. The proxy expects certificate files mounted at /etc/nginx/certs/cert.pem and /etc/nginx/certs/key.pem (the Compose file mounts your local ./certs directory). You can skip this part if you have some other certs and place them in /certs.
- Install
mkcert(https://github.com/FiloSottile/mkcert) for your platform and install the local CA once:
# install mkcert (platform-specific), then run once:
mkcert -install- Generate certs for localhost and loopback addresses:
mkdir -p certs
mkcert \
localhost \
127.0.0.1 \
::1 \
shoppinglist \
shoppinglist.local
- Start the stack and visit HTTPS locally:
docker compose up --build
# open https://localhost- This repo expects a local
nginxconfig atdocker/nginx/conf.d/default.conf(not committed).
The following sample config proxies HTTPS requests to the Node.js app running in Docker. It expects certificate files at /etc/nginx/certs/cert.pem and /etc/nginx/certs/key.pem, and forwards traffic from localhost or shoppinglist.local to the backend service. HTTP requests are redirected to HTTPS.
See docker/nginx/conf.d/default.conf for your local setup.
server {
listen 443 ssl;
server_name localhost shoppinglist.local;
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/certs/key.pem;
location / {
proxy_pass http://shoppinglist-app:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Optionally redirect HTTP to HTTPS
server {
listen 80;
server_name localhost shoppinglist.local;
return 301 https://$host$request_uri;
}