Skip to content
Personal

Notes App

A full-stack, multi-user note-taking app — Markdown, tags, sharing, version history — put through a dedicated security-hardening pass, not just a features pass.

Role
Design & development, solo
Year
2026
Stack
Node.js, Express, SQLite
Status
Complete

Notes App is a full-stack, multi-user note-taking application — Markdown notes with sanitized live preview, tagging, file attachments, controlled public sharing, full version history, and in-app notifications, all behind real authentication. It's hosted on Render's free tier: the server sleeps after inactivity, so the first request after idling can take 30–60 seconds to wake up, and the database resets on redeploy — register a fresh account to try it.

What makes it work

  • A real security-hardening pass, not a features checklist. bcrypt password hashing, JWT auth with no fallback signing secret (the server refuses to start without one), HttpOnly/SameSite cookies, a strict Helmet CSP with no inline scripts, and rate limiting on every sensitive endpoint.
  • Version history that never loses data. Every meaningful edit is snapshotted. Any historical version can be viewed and restored without erasing the versions that came after it — restoring is additive, not destructive.

Design at a glance

  • canvas · #faf7f2
  • surface · #ffffff
  • accent · #2f6e62
  • warm · #b8863c
  • ink · #211f1c
DisplayGeorgia / Palatino serif stack
BodySystem sans-serif stack

A genuine two-role type system — serif for headings, system sans for everything read at length — and a dark theme with its own retuned palette (canvas → #1b1a18, accent → #5aa695), not just an inverted light theme.

  • Note editor — pending capture

Built to last

  • Ownership checks on every resource — notes, attachments, versions, shares — scoped to the authenticated user
  • SQL parameterization throughout; sortable fields resolved through a server-side allowlist, never interpolated
  • A GET /health endpoint backed by a real database check, wired for genuine deployment monitoring

Want an app that takes "who can see what" this seriously?

See it live Start a project
For developers — the full technical breakdown

01. No fallback JWT secret, by design

Problem
Many apps ship a default or development signing secret that quietly ends up in production, making session tokens forgeable.
Choice
The server reads JWT_SECRET from the environment and refuses to start at all if it isn't set — no default, no silent fallback.
Result
It's structurally impossible to deploy this app with a guessable or missing signing secret.

02. Cross-user access returns 404, not 403

Problem
A 403 Forbidden response on someone else's note confirms that note exists — an information leak in its own right, even without exposing content.
Choice
Every ownership check returns a plain 404 for resources the requester doesn't own, indistinguishable from a resource that was never there.
Result
Verified during dedicated security testing that no endpoint reveals whether a given note, attachment, or version ID belongs to anyone.

Accessibility & performance

  • Strict Content-Security-Policy via Helmet — no inline scripts/styles, no third-party origins
  • All user-controlled values rendered via safe DOM APIs; Markdown sanitized with DOMPurify before display
  • Rate limiting on registration, login, password changes, account deletion, and public share-link access
  • Upload validation: MIME allowlisting, magic-byte verification, server-derived extensions, randomly generated storage filenames

What I’d do differently

No automated test suite yet — the security-focused testing (SQL injection payloads, XSS across every render path, IDOR boundaries between accounts, JWT tampering, path traversal, upload-bypass attempts) was extensive but manual. A v2 should move that coverage into CI so it stays enforced automatically as the app changes, not re-verified by hand each time.

Stack

  • Node.js & Express
  • SQLite (better-sqlite3)
  • JWT + bcrypt
  • Helmet + express-rate-limit
  • Multer (uploads)
  • marked + DOMPurify (Markdown)