- MAX_IMAGE_UPLOAD_MB: Controls Express JSON body limit for image data URLs. Increase if you see
PayloadTooLargeError.
Modern, fast notes app with a minimalist UI, responsive grid, and collaboration-ready backend. Built with React + Vite on the client and Express + Prisma (MySQL) on the server.
- Single dev process: server runs Vite middleware to serve the client during development.
- Database: Prisma ORM with MySQL; schema auto-pushed from code.
- Auth: JWT-based; first user is automatically an admin; admin-only invites.
- Preferences: per-user UI settings persisted and applied on login.
- Notes Grid: Responsive columns with stable width; FLIP animations on resize and reorder.
- Drag-to-Reorder: Swap or rearrange behavior (user preference); order persisted server-side.
- Interactive Checklists: Optimistic toggling; incomplete items shown first; completed items collapsible.
- Preferences: Note width, font, spacing, checkbox size, text size, drag behavior, animation speed, colors.
- Popovers & Menus: Widths match note card; footer actions fit without scrollbars.
- More Menu: Delete, Add Label, Uncheck All, Check All — anchored at card bottom-right.
- Invites: Admins can invite by email; each invite carries a desired role; first account is auto-admin.
FreemanNotes can extract readable text from note images server-side using PaddleOCR, then include that text in the existing global search bar.
- When an image is added to a note, the server returns immediately (no UI blocking).
- A background OCR job runs automatically:
- Decodes the image bytes (supports data URLs,
/uploads/..., and http/https URLs). - Preprocesses for accuracy (auto-orient, flatten alpha → white, contrast normalize, downscale large images).
- Runs PaddleOCR with angle classification (handles rotated text; best-effort deskew).
- Stores:
ocrText(raw extracted text)ocrSearchText(normalized text optimized for indexing)ocrDataJson(structured result: blocks/lines/boxes/confidence)ocrHash(sha256 of image bytes to avoid re-OCR)
- Decodes the image bytes (supports data URLs,
- When OCR completes, clients are notified via
note-images-changed, so search results update automatically.
- Docker (recommended): OCR works out of the box because the container image includes Python + PaddleOCR.
- Local development: OCR requires Python 3 and PaddleOCR deps. If Python (or deps) are missing, OCR fails safely and the app continues running.
- OCR defaults to English (
lang="en"). - The OCR service is designed to accept a
langparameter later (PaddleOCR supports many languages).
- Labels UI: Inline chips and a small labels editor popover.
- Invite UX: Auto-fill invite token from
/?invite=; invite list page for admins. - Email Verification: Optional verify-on-register.
- Invite Expiry: Expiring tokens; admin-configurable.
- More Animations: Subtle transitions for popovers and editor state.
- Copy
.env.exampleto.envand set values:
PORT=4000
APP_URL=http://localhost:4000
NODE_ENV=development
# MySQL connection (example)
DATABASE_URL="mysql://user:pass@host:3306/freeman"
# JWT secret
JWT_SECRET=generate_a_long_random_string
# Web Push (PWA notifications)
# Generate with: npm run push:gen-vapid
VAPID_PUBLIC_KEY=...
VAPID_PRIVATE_KEY=...
VAPID_SUBJECT=mailto:admin@example.com
# Registration mode
USER_REGISTRATION_ENABLED=true
# SMTP (optional for invites)
SMTP_HOST=...
SMTP_PORT=587
SMTP_USER=...
SMTP_PASS=...
INVITE_FROM=...
APP_BASE_URL=http://localhost:4000
- Install dependencies and set up the DB:
npm ci
npm run setup-db # runs prisma db push
npx prisma generate # if needed
- Start development (single process: server + Vite middleware):
npm run dev
# Open http://localhost:4000
# Build client and server
npm run build
# Start the compiled server (serves client from client-dist)
npm start
# Open http://localhost:4000
This app supports real background notifications via Web Push (service worker push events). Install-time permission prompts are not allowed by browsers; users must enable notifications via an in-app click.
Setup:
- Ensure your database is up to date after pulling changes:
npm run setup-db. - Generate VAPID keys:
npm run push:gen-vapid. - Set
VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY,VAPID_SUBJECTin.env. - Push requires HTTPS on real devices (localhost is the usual exception).
Testing (in the app):
- Preferences → Notifications → Enable notifications
- Local test (verifies service-worker
showNotification) - Push test (verifies subscription + server send)
# Build image
docker build -t freemannotes:latest .
# Run with .env
docker run -p 4000:4000 --env-file .env freemannotes:latest
Or with Compose:
docker compose up --build
User-uploaded files are served under /uploads (e.g. avatars at /uploads/users/{id}.jpg). Note images uploaded from the UI are also persisted here (under /uploads/notes/...).
In Docker, you should persist this directory using a volume; otherwise files will be lost when the container is replaced.
- UPLOADS_DIR: Filesystem path where the server reads/writes uploads.
- Default:
./uploads(relative to the server working directory) - Compose default:
/app/uploads(with a named volume mounted)
- Default:
Docker named volumes typically live under Docker's data-root (often on cache in Unraid). To avoid note images filling cache space, use a bind mount to a host path on the array.
With the provided Compose file, set these in your .env:
# Container path
UPLOADS_DIR=/app/uploads
# Host path (example) -> store on array share
UPLOADS_VOLUME=/mnt/user/freemannotes/uploads
Now docker compose up --build will mount that host directory into the container and all uploads (avatars + note images) will be stored there.
Compose already includes an uploads_data named volume for the app service. When updating, avoid docker compose down -v unless you intentionally want to delete uploads.
If you run via docker run, mount a volume and set UPLOADS_DIR:
docker run -p 4000:4000 --env-file .env -e UPLOADS_DIR=/app/uploads -v freemannotes_uploads:/app/uploads freemannotes:latest
Pulling a new app image should not reset your database as long as your database storage is persistent.
- If you run MySQL as a container, make sure it uses a named volume (do not use an ephemeral container filesystem).
- Avoid destructive Prisma commands in production like
prisma migrate resetorprisma db push --force-reset. - For public/production deployments, prefer Prisma Migrate (
prisma migrate deploy) rather than relying onprisma db push.migrate deployapplies versioned SQL migrations in order and is the standard upgrade path.db pushis great for prototyping, but it is not a robust “upgrade mechanism” for apps in the wild.
- This app applies schema changes at startup via
ensureDatabaseReady()and does not intentionally wipe data in production.
The included docker-compose.yml has an optional MySQL service behind a profile that uses a named volume.
- Set
DATABASE_URLin your.envto:
DATABASE_URL="mysql://freeman:freemanpass@db:3306/freemannotes"
- Start compose with the DB profile:
docker compose --profile with-db up --build
- When updating, do not run
docker compose down -v(that deletes volumes).
- Uses Semantic Versioning (MAJOR.MINOR.PATCH).
- When you say a milestone is done, we will bump and tag:
- Patch:
npm run release:patch - Minor:
npm run release:minor - Major:
npm run release:major
- Patch:
- Each release updates
package.jsonversion, creates a Git tag (e.g.0.5.0), and pushes to GitHub. - Maintain notes in CHANGELOG.md.
- Fix: Grid packing after filters — Recalculates masonry row spans when label filters or search change, ensuring notes snap back to the correct layout after clearing filters. Implemented by dispatching
notes-grid:recalcinclient/src/components/NotesGrid.tsxwheneverselectedLabelIds,searchQuery, ornoteschange.
-
POST /api/auth/register— Register (invite required if registration disabled). -
POST /api/auth/login— Login; returns JWT. -
GET /api/auth/me— Current user (JWT required). -
PATCH /api/auth/me— Update user prefs. -
GET /api/config— Lightweight config for client (registration enabled flag). -
POST /api/invite— Admin-only; create invite and send email. -
GET /api/notes— List notes (owner and collaborators). -
POST /api/notes— Create note (new notes appear at top-left). -
PATCH /api/notes/order— Persist note order. -
PATCH /api/notes/:id— Update note. -
DELETE /api/notes/:id— Delete note (cascades child rows). -
PATCH /api/notes/:noteId/items/:itemId— Update checklist item. -
PUT /api/notes/:id/items— Replace/sync checklist items. -
POST /api/notes/:id/labels— Add/create label and link to note. -
POST /api/notes/:id/images— Add image to note. -
GET /api/notes/:id/images— List images for a note. -
DELETE /api/notes/:id/images/:imageId— Delete an image from a note. -
POST /api/notes/:id/images/:imageId/ocr— Trigger OCR for an image (async).
- Dev server not reading .env: Restart after edits; confirm
GET /api/configreflectsUSER_REGISTRATION_ENABLED. - Windows Prisma EPERM (rename): Remove temp files then regenerate:
Remove-Item -Force node_modules\.prisma\client\query_engine-windows.dll.node.tmp*npx prisma generate
- Invite emails: Ensure SMTP vars are set; server logs show send status.
- Registration disabled: Provide
inviteTokenin register body; invite must be unused and match the email.
node_modules/is ignored by design; dependencies install frompackage.json..envis ignored; keep.env.examplein the repo for configuration guidance.