A personal bookmark manager with tag filtering, backed by SQLite. Publicly readable, password-protected for edits.
Marx is a single-file Val Town HTTP val. Everything — routing, auth, database queries, and HTML rendering — lives in main.ts.
Storage: Uses Val Town's built-in SQLite via std/sqlite. Three tables:
bookmarks — stores URL, title, description, and save datetags — unique tag namesbookmark_tags — many-to-many join tableAuth: A single password set via an environment variable. On login, the password is hashed with SHA-256 to produce a session token, which is stored as an HttpOnly cookie valid for 30 days. No user accounts — it's single-owner.
Public vs. private: Anyone can browse bookmarks and filter by tag. Only authenticated users see edit/delete controls and can call the mutation APIs.
Routing is handled manually in the default export function:
| Route | Method | Auth required |
|---|---|---|
/ | GET | No |
/tags/:slug/ | GET | No |
/login | GET / POST | No |
/logout | GET | No |
/api/bookmarks/update | POST | Yes |
/api/bookmarks/delete | POST | Yes |
/api/tags/rename | POST | Yes |
/api/tags/delete | POST | Yes |
/api/import | POST | Yes |
On Val Town, open snptrs/marx and click Fork.
In your val's environment settings, add:
AUTH_PASSWORD=your-password-here
The val doesn't auto-create its tables. Run this once as a Val Town script to set up the schema:
import { sqlite } from "https://esm.town/v/std/sqlite/main.ts";
await sqlite.execute(`
CREATE TABLE IF NOT EXISTS bookmarks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
title TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT '',
saved TEXT NOT NULL DEFAULT (datetime('now'))
)
`);
await sqlite.execute(`
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
)
`);
await sqlite.execute(`
CREATE TABLE IF NOT EXISTS bookmark_tags (
bookmark_id INTEGER NOT NULL REFERENCES bookmarks(id),
tag_id INTEGER NOT NULL REFERENCES tags(id),
UNIQUE(bookmark_id, tag_id)
)
`);
POST a JSON array to /api/import with a Cookie header containing your session token, or just log in to your instance first and use your browser session.
[ { "url": "https://example.com", "title": "Example Site", "description": "Optional notes", "saved": "2024-06-01T12:00:00Z", "tags": ["design", "reference"] } ]
Once logged in: