{"id":31655517,"url":"https://github.com/ndrean/htmz","last_synced_at":"2025-10-07T13:15:07.312Z","repository":{"id":314571991,"uuid":"1056020175","full_name":"ndrean/htmz","owner":"ndrean","description":"Shopping cart with http.zig Webserver with SQLite, HTMX","archived":false,"fork":false,"pushed_at":"2025-09-30T06:06:55.000Z","size":31572,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-30T08:21:05.089Z","etag":null,"topics":["htmx-app","sqlite3","websockets","zig"],"latest_commit_sha":null,"homepage":"https://httpz.htmz.online","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ndrean.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-13T08:19:11.000Z","updated_at":"2025-09-30T06:06:58.000Z","dependencies_parsed_at":"2025-09-13T10:50:31.062Z","dependency_job_id":"26c97111-6ef4-4cb4-935a-f56f87d9e081","html_url":"https://github.com/ndrean/htmz","commit_stats":null,"previous_names":["ndrean/htmz"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ndrean/htmz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fhtmz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fhtmz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fhtmz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fhtmz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ndrean","download_url":"https://codeload.github.com/ndrean/htmz/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ndrean%2Fhtmz/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278780089,"owners_count":26044498,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["htmx-app","sqlite3","websockets","zig"],"created_at":"2025-10-07T13:15:06.160Z","updated_at":"2025-10-07T13:15:07.306Z","avatar_url":"https://github.com/ndrean.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HTMZ\n![Zig support](https://img.shields.io/badge/Zig-0.15.1-color?logo=zig\u0026color=%23f3ab20)\n![Static Badge](https://img.shields.io/badge/http-zig-yellow)\n[![SQLite](https://img.shields.io/badge/SQLite-%2307405e.svg?logo=sqlite\u0026logoColor=white)](#)\n[![HTMX](https://img.shields.io/badge/HTMX-36C?logo=htmx\u0026logoColor=fff)](#)\n\nIt displays a grocery list where you can view the details of each product. It holds the price.\nYou can select a product to add to the shopping list. A total number of items gets updated.\nIn the shopping cart, you can increase/decrease the quantity and remove the item. The total value is calculated.\n\n- `SQLite` with filed-based storage\n- HashMap to hold the users' shopping cart\n- WebSocket to remove the shopping cart when disconnects\n\n## Tests\n\nSettings:  `httpz`, 2 workers (2 VCPU), 32 thread_pool, SQLite 2 pool, ulimit = 131_000.\n\nLocal `Grafana k6` stress tests results: 10 req/s/VU\n\n```sh\nk6 run load-test/progressive-test.js\n```\n\n```txt\n=== PROGRESSIVE LOAD TEST: PLATEAU PERFORMANCE ===\nPLATEAU 1 (5K VUs - 30s):\n  Requests: 949232\n  Req/s: 31641\n  Avg Response Time: 4.62ms\n  95th Percentile: 26.79ms\n\nPLATEAU 2 (10K VUs - 30s):\n  Requests: 1408818\n  Req/s: 46961\n  Avg Response Time: 39.29ms\n  95th Percentile: 154.88ms\n\n=== OVERALL RESULTS ===\nPeak VUs: 10000\nTotal Requests: 4082576\nFailed Requests: 0.00%\n```\n\n- Remote `k6` stress tests on a production deployment (a small `Hetzner` VPS). 5 req/s/VU\n\nSettings:  `httpz`, 2 workers (2 VCPU), 32 thred_pool, SQLite 2 pool, `ulimit` = 131_000.\n\n```sh\nk6 run load-test/hetzner.js\n```\n\n```txt\n=== PROGRESSIVE LOAD TEST: PLATEAU PERFORMANCE ===\nPLATEAU 1 (2K VUs - 30s):\n  Requests: 262896\n  Req/s: 8763\n  Avg Response Time: 27.66ms\n  95th Percentile: 32.10ms\n\nPLATEAU 2 (6K VUs - 30s):\n  Requests: 754194\n  Req/s: 25140\n  Avg Response Time: 36.08ms\n  95th Percentile: 53.17ms\n\n=== OVERALL RESULTS ===\nPeak VUs: 6000\nTotal Requests: 1704638\nFailed Requests: 0.00%\n```\n\n## HTMX Cart Update Flow\n\nThis diagram shows how cart operations trigger real-time updates for both cart count and total:\n\n### Scenario 1: Adding Item from Grocery List\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant Browser\n    participant Server\n    participant CartTotal as Cart Total Element\n\n    User-\u003e\u003eBrowser: Clicks \"Add to Cart\" button\n    Browser-\u003e\u003eServer: POST /api/cart/add/42\n    Server-\u003e\u003eServer: Add item to user's cart\n    Server--\u003e\u003eBrowser: 200 OK + HX-Trigger: updateCartCount, cartUpdate\n    Browser-\u003e\u003eBrowser: Fires 'updateCartCount' event\n    Browser-\u003e\u003eBrowser: Fires 'cartUpdate' event\n    Browser-\u003e\u003eServer: GET /cart-count (triggered by updateCartCount)\n    Server--\u003e\u003eBrowser: \"3\" (new cart count)\n    Browser-\u003e\u003eBrowser: Updates cart badge to \"3\"\n    CartTotal-\u003e\u003eServer: GET /cart-total (hx-trigger=\"cartUpdate from:body\")\n    Server--\u003e\u003eCartTotal: \"$15.50\" (new total)\n    Browser-\u003e\u003eBrowser: Updates cart total display\n```\n\n### Scenario 2: Changing Quantity in Shopping Cart\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant Browser\n    participant Server\n    participant CartTotal as Cart Total Element\n\n    User-\u003e\u003eBrowser: Clicks \"+\" button on cart item\n    Browser-\u003e\u003eServer: POST /api/cart/increase-quantity/42\n    Server-\u003e\u003eServer: Increment item quantity\n    Server--\u003e\u003eBrowser: 200 OK + \"2\" + HX-Trigger: updateCartCount, cartUpdate\n    Browser-\u003e\u003eBrowser: Updates quantity display to \"2\"\n    Browser-\u003e\u003eBrowser: Fires 'updateCartCount' event\n    Browser-\u003e\u003eBrowser: Fires 'cartUpdate' event\n    Browser-\u003e\u003eServer: GET /cart-count (triggered by updateCartCount)\n    Server--\u003e\u003eBrowser: \"4\" (new cart count)\n    Browser-\u003e\u003eBrowser: Updates cart badge to \"4\"\n    CartTotal-\u003e\u003eServer: GET /cart-total (hx-trigger=\"cartUpdate from:body\")\n    Server--\u003e\u003eCartTotal: \"$20.50\" (new total)\n    Browser-\u003e\u003eBrowser: Updates cart total display\n```\n\n**Key HTMX Concepts:**\n\n- `hx-trigger=\"load, cartUpdate from:body\"` - Element listens for events\n- `HX-Trigger: cartUpdate` header - Server tells browser to fire events\n\n## Setup assets\n\nCopy the `.gz` versions of _index.html_, _index.css_, _htmx.min.js_ and _ws.min.js_  into \"/public\",\nand symlink the SVGs from src/html/svg into public/svg\n\n```sh\npnpm --filter htmz update --recursive\n```\n\n```sh\npnpm --filter htmz make:public\nln -s src/html/svg public/svg\n```\n\n## Memory leak detection\n\nMemory leak detection on in Debug mode with `std.posix.sigaction` (cf [this example](https://github.com/karlseguin/http.zig/blob/master/examples/09_shutdown.zig)).\n\n### Memory Monitoring Commands Local Development\n\n```sh\n# Get process ID and initial memory baseline\nSERVER_PID=$(pgrep -f 'zig-out/bin/htmz')\nps -o pid,vsz,rss,comm -p $SERVER_PID\n\n# Monitor memory during stress test\nps -o pid,vsz,rss,comm -p $SERVER_PID \u0026\u0026 date\n```\n\n### Run failure stress test to check memory behavior\n\n```sh\nk6 run load-test/failure-test.js\n```\n\n### VPS memory monitor\n\n```sh\n# Get process ID and initial memory baseline (adjust path for VPS)\ncd opt/htmz\nSERVER_PID=$(pgrep -f 'htmz')\nps -o pid,vsz,rss,comm -p $SERVER_PID\n\n# Alternative if pgrep fails - find by process name\nSERVER_PID=$(ps aux | grep '[h]tmz' | grep -v grep | awk '{print $2}')\nps -o pid,vsz,rss,comm -p $SERVER_PID\n\n# Monitor memory during stress test\nps -o pid,vsz,rss,comm -p $SERVER_PID \u0026\u0026 date\n\n# Check memory usage from /proc (more detailed)\ncat /proc/$SERVER_PID/status | grep -E 'VmRSS|VmSize'\n```\n\n## Deploy on `Hetzner`\n\nCX22 machine, 2 VCPU x86, 4GB RAM machine with `Debian`.\n  \n- Static assets (HTML, CSS, SVG, HTMX.JS) are compiled into the code.\n- The `SVG`s are saved as text into the SQLite database, and then fetched and interpolated as text into the rendered template.\n\nProduction built:\n\n```sh\n# build with target\nzig build -Dtarget=x86_64-linux-gnu -Doptimize=ReleaseFast\n```\n\n- copy local binaries and assets to the VPS\n\n```sh\nscp ./zig-out/bin/htmz root@ipv4-address:opt/htmz/htmz-httpz\n\n# the SVGs are saved in the /public/ssvsg folder. SQLite fills its \"items\" table with the SVGs as text.\nscp -r public/ root@ipv4-address:opt/htmz/\n```\n\nIn the VPS, test:\n\n```sh\nroot@debian-4gb-nbg1-1:~ cd opt/htmz \u0026\u0026 chmod +x htmz-httpz\n\nroot@debian-4gb-nbg1-1:~ SECRET_KEY=ziggit ./htmz-httpz\n```\n\n- hammer the VPS from home: the `httpz` server is listening on port 8880 (to be `Cloudflare` compatible).\n\n```sh\nBASE_URL=http://ipv4-address:8880 k6 run load-test/progressive-test.js \n```\n\n### Daemonize - Create service\n\n```sh\nroot@debian-4gb-nbg1-1:~ \n\n\nsudo tee /etc/systemd/system/htmz-httpz.service \u003e /dev/null \u003c\u003c 'EOF'\n[Unit]\n  Description=HTMZ Web Server (httpz implementation)\n  After=network.target\n\n[Service]\n  Type=simple\n  User=root\n  WorkingDirectory=/root/opt/htmz\n  Environment=LD_LIBRARY_PATH=/root/opt/htmz/lib\n  Environment=SECRET_KEY=ziggit\n  ExecStart=/root/opt/htmz/htmz-httpz\n  Restart=always\n  # Set file descriptor limits\n  LimitNOFILE=65536\n  LimitNPROC=65536\n\n[Install]\n  WantedBy=multi-user.target\nEOF\n```\n\nStart/stop the servce:\n\n```sh\nroot@debian-4gb-nbg1-1:~ sudo systemctl daemon-reload\n# auto-start on boot\nroot@debian-4gb-nbg1-1:~ sudo systemctl enable htmz-httpz\n\nroot@debian-4gb-nbg1-1:~ sudo systemctl start htmz-httpz\nroot@debian-4gb-nbg1-1:~ sudo systemctl status htmz-httpz\n\nroot@debian-4gb-nbg1-1:~ sudo systemctl stop htmz-httpz\nroot@debian-4gb-nbg1-1:~ sudo systemctl restart htmz-httpz\n```\n\nLogs:\n\n```sh\nsudo journalctl -u htmz-httpz -f   # Live logs\nsudo journalctl -u htmz-httpz      # All logs\n```\n\nCheck if the server is running: \u003chttps://httpz.htmz.online\u003e\n\n## Notes\n\n`-v` lets you inspect the headers so you can see when cookies are being set and sent.\n\n- grab a cookie for testing:\n\n```sh\ncurl -v --cookie-jar cookies.txt http://localhost:8080/\n```\n\n(or `-v -c`)\n\n- pass the cookie for testing a POST endpoint:\n\n```sh\ncurl -v --cookie cookies.txt -X POST http://91.98.129.192:8080/api/cart/add/1\n```\n\n(or `-v -b`)\n\n- Useful!:\n\n```sh\npkill -f \"htmz\"\n\npgrep -f htmz\n# \u003cpid\u003e \nlsof -it:8880 | xargs kill \u003cpid\u003e\n```\n\n## Results\n\n### local\n\n```txt\n📊 PLATEAU 1 (2K VUs - 30s):\n  Requests: 159094\n  Req/s: 5303\n  Avg Response Time: 26.84ms\n  95th Percentile: 30.55ms\n\n📊 PLATEAU 2 (6K VUs - 30s):\n  Requests: 473560\n  Req/s: 15785\n  Avg Response Time: 29.00ms\n  95th Percentile: 36.36ms\n\n=== OVERALL RESULTS ===\nPeak VUs: 6000\nTotal Requests: 1722822\nFailed Requests: 0.00%\n```\n\n### Deployed on VPS\n  \n```txt\n=== PROGRESSIVE LOAD TEST: PLATEAU PERFORMANCE ===\n📊 PLATEAU 1 (2K VUs - 30s):\n  Requests: 262914\n  Req/s: 8764\n  Avg Response Time: 27.67ms\n  95th Percentile: 32.66ms\n\n📊 PLATEAU 2 (8K VUs - 30s):\n  Requests: 899734\n  Req/s: 29991\n  Avg Response Time: 59.65ms\n  95th Percentile: 113.91ms\n\n=== ZIG VPS OVERALL RESULTS ===\nPeak VUs: 8000\nTotal Requests: 2223424\nFailed Requests: 0.00%\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndrean%2Fhtmz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fndrean%2Fhtmz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fndrean%2Fhtmz/lists"}