A simple self-hosted shopping list.
  • Python 41%
  • HTML 28.8%
  • JavaScript 16.5%
  • CSS 13%
  • Shell 0.4%
  • Other 0.3%
Find a file
zees 4280027b8c fix: version static asset URLs to defeat stale HTTP/SW caches
The real cause of stale PWAs: app.js/main.css were referenced by bare URLs
with no Cache-Control, so browsers heuristically cached them and served old
bytes even after the SW cache was cleared. Reference them as ?v=<hash> where
<hash> is a content hash of the assets (config.ASSET_VERSION), so every change
is a brand-new URL that cannot hit a stale cache entry. index.html is served
network-first, so even an already-stale PWA requests the new URL on next open.
The dynamic /sw.js injects the same version into its pre-cache list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:58:22 +02:00
data Initial commit 2026-02-14 22:24:09 +01:00
routes fix: upgrade FastAPI/Starlette/Pydantic/Jinja2 and adopt new TemplateResponse signature 2026-06-26 10:33:09 +02:00
static fix: version static asset URLs to defeat stale HTTP/SW caches 2026-06-26 10:58:22 +02:00
templates fix: version static asset URLs to defeat stale HTTP/SW caches 2026-06-26 10:58:22 +02:00
translations feat: add 'check for updates' button to profile page 2026-06-26 10:48:15 +02:00
.env.example Initial commit 2026-02-14 22:24:09 +01:00
.gitignore Initial commit 2026-02-14 22:24:09 +01:00
__init__.py fix: merge issues 2026-02-15 11:38:43 +01:00
auth.py fix: Fixed #27 2026-02-17 20:38:55 +01:00
config.py fix: version static asset URLs to defeat stale HTTP/SW caches 2026-06-26 10:58:22 +02:00
config.yaml fix: Fixed #26 2026-02-17 20:56:14 +01:00
database.py fix: shopping mode is not universal for all users and devices the same 2026-06-25 19:43:30 +02:00
docker-compose.yml fix: interface 2026-02-15 21:45:55 +01:00
Dockerfile fix: ratelimit 2026-03-21 06:39:05 +01:00
LICENSE Initial commit 2026-02-14 16:23:37 +01:00
main.py fix: version static asset URLs to defeat stale HTTP/SW caches 2026-06-26 10:58:22 +02:00
pyproject.toml chore: version number 2026-02-21 14:39:30 +01:00
ratelimit.py feat: rate limiting 2026-03-21 06:33:53 +01:00
README.md fix: readme 2026-02-22 22:22:21 +01:00
requirements.txt fix: upgrade FastAPI/Starlette/Pydantic/Jinja2 and adopt new TemplateResponse signature 2026-06-26 10:33:09 +02:00
start.sh fix: port 2026-02-15 13:54:55 +01:00
websocket.py refactored responsibilities 2026-02-15 08:03:33 +01:00

🛒 Einkaufsliste

A self-hosted, very simple shopping list app.

No "schnickschnack".

Features

  • Multiple Lists: For different shopping locaionts, there are different lists.
  • Multiple Users: Several users share lists when they belong to a household.
  • Sync: Sync in real-time with serverx
  • Proposal List: Fill the shopping list from a proposal list what is availble in the store
  • Shopping Mode: A simple list where it is possible to cross off items

What it is not

  • If you are looking for managing your groceries at home, have a look at Grocy
  • If you want to copy items from recipes to a shopping list, visit Tandoor or Mealie.
  • If you are looking for managing lists in General, have a look at Jotty.

Note

I am a software engineer (currently in the role as a Software Architect) with more than 25 years of experience in software development. Still, with this project I wanted to experience how I can utilize an AI agent. I am learning with this project about the usage of automated coding and also on web development with Python (which I have not yet done in my career). Therefore, if you like the approach and want to contribute to make things better, you are very welcome. Otherwise you may have a look at other projects on Codeberg.

Have fun!

Docker

git clone https://codeberg.org/deinname/einkaufsliste.git
cd einkaufsliste

# create .env and generate secret key
cp .env.example .env
echo "SECRET_KEY=$(openssl rand -hex 32)" >> .env

# start
docker compose up -d

Check the terminal or log for the admin link:

docker compose logs app

→ Open the link in browser, set a password. Done.
Add additional users via the admin page.

Reverse Proxy (nginx)

The app is running under 127.0.0.1:7953 und using a reverse proxy is recommended. WebSocket support is mandatory (Upgrade-Header).

Example configuration for nginx:

proxy_read_timeout 86400s;
proxy_send_timeout 86400s;

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self' ws: wss:; img-src 'self' data:;" always;


# Never cache the service worker
location = /sw.js {
    proxy_pass http://<host>:7953;
    add_header Cache-Control "no-cache, no-store, must-revalidate";
}

limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

location /login {
    limit_req zone=login burst=3 nodelay;
    proxy_pass http://<host>:7953;
}


Without Docker

Run directly on the host.

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

export SECRET_KEY=$(openssl rand -hex 32)
uvicorn main:app --host 127.0.0.1 --port 7953

or with the start script:

chmod +x start.sh && ./start.sh

Configuration

config.yaml contains households, market types and proposal lists.

Backup

The database can be found under data/shopping.db (SQLite).

To perform a simple backup:

cp data/shopping.db data/shopping.db.bak

Tech Stack

  • Backend: Python, FastAPI, uvicorn
  • Datenbank: SQLite via aiosqlite
  • Echtzeit: WebSockets
  • Auth: JWT-Cookies, bcrypt, Einladungstoken
  • Frontend: Vanilla JS, kein Framework