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
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 /healthendpoint backed by a real database check, wired for genuine deployment monitoring
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_SECRETfrom 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)