A lightweight, serverless note-taking web app written in TypeScript. Create, edit, and share notes with auto-save. Deploys on Cloudflare Workers (native KV) or Vercel Edge (Upstash Redis). No JS frameworks, no build step, zero dependencies — pure vanilla HTML/CSS/JS.
- Simple Note Editor: Lightweight web interface for creating and editing notes
- Auto-Save: Automatically saves note content every second
- Shareable URLs: Notes accessible via direct links with human-friendly WORDnn IDs
- Multi-Deployment: Cloudflare Workers or Vercel Edge
- Zero Dependencies: No npm runtime deps — KV via native bindings (Cloudflare) or REST fetch (Vercel)
- XSS Protection: User content is HTML-escaped
- Responsive: Works on desktop and mobile
- Print Support: Print-friendly interface
- Node.js 24+
The app uses Wrangler (Cloudflare Workers) for local dev. KV is simulated by Miniflare — no account needed:
npm install
npm run devOpen http://localhost:8787.
Storage: Cloudflare KV — persistent, native, zero-config.
npx wrangler kv:namespace create "KV"Copy the ID from the output and paste it:
[[kv_namespaces]]
binding = "KV"
id = "your-namespace-id"npm run deployYour app is live at https://your-app.your-account.workers.dev with persistent KV storage.
Storage: Upstash Redis — persistent with 7-day TTL.
Create a Redis database at upstash.com (free tier). Copy the KV_REST_API_URL and KV_REST_API_TOKEN from the REST API section.
Alternatively, create via Vercel Dashboard: Storage → Create → Redis. This auto-injects the env vars.
npx vercel env add KV_REST_API_URL production --value <your-url>
npx vercel env add KV_REST_API_TOKEN production --value <your-token>npx vercel --prodvercel.json builds src/vercel.ts as an Edge Function and routes all requests to it. The app uses VercelKVStorage which connects to Upstash Redis via its REST API.
Your app is live at https://your-app.vercel.app with persistent KV storage.
The repository includes two workflows:
Runs on every push/PR to feature/typescript:
npm ci→tsc --noEmit→npm test
Manual trigger (workflow_dispatch) — typecheck → deploy to Cloudflare Workers.
Required secret: CF_API_TOKEN — Cloudflare API token with Workers permissions (create in Cloudflare Dashboard → My Profile → API Tokens, template: "Edit Cloudflare Workers").
Manual trigger (workflow_dispatch) — typecheck → deploy to Vercel Edge.
Required secrets:
| Secret | Description |
|---|---|
VERCEL_TOKEN |
Vercel access token (create in Vercel Dashboard → Settings → Tokens) |
VERCEL_ORG_ID |
Your Vercel team/org ID (from .vercel/project.json) |
VERCEL_PROJECT_ID |
Your Vercel project ID (from .vercel/project.json) |
To set up secrets: Go to your GitHub repo → Settings → Secrets and variables → Actions, then add each value. Run workflows via Actions tab → select workflow → Run workflow.
Retrieve and display a note.
Parameters:
noteId(path) ornote(query): Note ID (alphanumeric, 3–32 chars, no I/O/0/1)
Notes:
- The preferred URL format is
/noteid/{noteId}for shell-friendly links (e.g.,http://example.com/noteid/BLAST47). - Backwards compatibility:
/?note={noteId}still works. - Links use the request host or
x-forwarded-*headers for reverse proxy support. - Requests with
User-Agentcontaining "curl" return plain text instead of HTML.
Save or delete a note.
Request body (JSON):
{
"noteId": "BLAST47",
"content": "Note content here"
}Response (JSON):
{
"success": true,
"noteId": "BLAST47"
}Behavior:
- If
noteIdis empty, a random WORDnn ID is generated (e.g., "BLAST47") - If
contentis empty or whitespace-only, the note is deleted - Otherwise, the note is saved
Examples:
# Create new note
curl -X POST https://your-app.com/ \
-H "Content-Type: application/json" \
-d '{"content":"Hello World"}'
# Returns URL for curl requests
curl -X POST https://your-app.com/ \
-H "Content-Type: application/json" \
-H "User-Agent: curl/8.0" \
-d '{"content":"Hello World"}'
# → https://your-app.com/noteid/BLAST47.
├── src/
│ ├── workers.ts # Cloudflare Workers entry point
│ ├── vercel.ts # Vercel Edge entry point
│ ├── handler.ts # HTTP handlers (GET, POST, OPTIONS)
│ ├── handler.test.ts # Handler unit tests
│ └── lib/
│ ├── template.ts # Full HTML/CSS/JS UI template
│ ├── storage.ts # Storage interface + KV / Vercel KV backends
│ ├── storage.test.ts # Storage unit tests
│ ├── utils.ts # Note ID generation/validation, HTML escaping, ClientIP
│ └── types.ts # TypeScript type definitions
├── wrangler.toml # Cloudflare Workers configuration
├── vercel.json # Vercel deployment configuration
├── .vercelignore # Files excluded from Vercel deployment
├── .github/workflows/ # GitHub Actions CI/CD workflows
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
└── README.md # This file
| Command | Description |
|---|---|
npm run dev |
Start local dev server with Wrangler |
npm run deploy |
Deploy to Cloudflare Workers |
npm test |
Run Vitest unit tests |
npm run typecheck |
Run TypeScript type check (tsc --noEmit) |
Note IDs use a WORDnn format — a random dictionary word (3–5 uppercase letters, excluding ambiguous characters I/O/0/1) followed by 2 digits. Examples: BLAST47, ACE23, ZEBRA99.
Both platforms use persistent KV storage with 7-day TTL. Cloudflare Workers uses native Workers KV via env.KV. Vercel Edge uses Upstash Redis via REST API (KV_REST_API_URL / KV_REST_API_TOKEN). Both implement the same Storage interface.
npm test # Run Vitest
npm run typecheck # TypeScript type checking- Input validation: Note IDs restricted to
[A-HJ-NP-Z2-9]{3,32} - XSS protection: User content server-side HTML-escaped before rendering
- No third-party frontend dependencies
MIT License — feel free to use and modify for your needs.