A note-taking app that doubles as a vocabulary builder. You write notes the way you would in Google Keep; the backend reads them, picks out the words it judges difficult, looks up definitions, and turns them into flashcard-style quizzes you can mark as "known".
Working today
- Masonry note grid — a Pinterest-style layout (
masonic) with pinned notes in their own section, backed by the/api/notespersistence layer. - Vocabulary extraction & quiz mode — click a note to see the difficult words it contains with definitions, or open quiz mode to step through them one at a time and dismiss the ones you already know.
- Animated UI — spring-physics transitions throughout (
framer-motion) on sidebars, note expansion, and card hovers. - Smart note creation — a minimal expanding text-bar inspired by modern search interfaces.
See Project status for what remains.
notes-2.0/
├── notes2.0/ # Frontend (React Router v7 + Vite)
├── backend/ # API + persistence (FastAPI + SQLAlchemy)
│ ├── main.py # App entrypoint: CORS, /health, router wiring
│ ├── app/
│ │ ├── api/ # FastAPI routers (users, notes, words)
│ │ ├── crud/ # Database operations
│ │ ├── db/ # SQLAlchemy models + session factory
│ │ ├── schemas/ # Pydantic request/response models
│ │ └── services/ # NLP / vocabulary analysis
│ └── alembic/ # Database migrations
├── docker-compose.yml # Frontend + backend + PostgreSQL
└── README.md # This file
Frontend (notes2.0/)
- Framework: React Router v7 (Vite), React 19
- Language: TypeScript
- Styling: Tailwind CSS v4 +
shadcn/uiand Base UI components - Animations: Framer Motion
- Layout: Masonic (masonry grid)
Backend (backend/)
- Framework: FastAPI (Python 3.12)
- Database: PostgreSQL 15 via SQLAlchemy 2.0 ORM, migrations with Alembic
- NLP / Analysis: NLTK (
nltk) and TextStat (textstat)
Three tables. A user owns many notes; notes and word definitions are linked many-to-many
through a note_word association table, so one definition is shared across every note that
uses the word.
User ──< Note >──note_word──< WordDefinition
Deleting a user cascades to their notes. Deleting a note or a word only removes the link between them, never the row on the other side.
All routes are prefixed with /api. Interactive docs are served at /docs once running.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness check (not under /api) |
POST |
/api/users |
Create a user (409 if the email is taken) |
GET |
/api/users |
List users (skip, limit) |
GET/PATCH/DELETE |
/api/users/{id} |
Read, partially update, or delete a user |
POST |
/api/notes |
Create a note (404 if the owner doesn't exist) |
GET |
/api/notes |
List notes, optionally filtered by ?user_id= |
GET/PATCH/DELETE |
/api/notes/{id} |
Read, partially update, or delete a note |
POST/DELETE |
/api/notes/{id}/words/{word_id} |
Link or unlink a word and a note |
POST |
/api/words |
Create a word definition |
GET |
/api/words |
List definitions, or look one up with ?word= |
GET/PATCH/DELETE |
/api/words/{id} |
Read, partially update, or delete a definition |
PATCH bodies only need the fields being changed; omitted fields are left untouched.
The quickest path is the PostgreSQL service in docker-compose.yml:
docker compose up -d dbConfiguration comes from .env at the repo root (POSTGRES_USER, POSTGRES_PASSWORD,
POSTGRES_DB, POSTGRES_PORT, and the DATABASE_URL the backend reads).
Note: the defaults in
docker-compose.ymland the values in.envdiffer — compose falls back to passwordpostgres, while.envsetsmysecretpassword. Since compose reads.env, the file wins; just don't rely on the inline defaults.
cd backend
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
alembic upgrade head # see the caveat in Project status first
uvicorn main:app --reload --port 8000The API listens on http://127.0.0.1:8000, with docs at http://127.0.0.1:8000/docs.
cd notes2.0
npm install
npm run devAvailable at http://localhost:5173. Loaders and actions call the backend server-side through
app/lib/api.server.ts, which reads API_URL and falls back to http://localhost:8000 — so
start the API first. Because the browser never calls the backend directly, there is no CORS to
configure for this path.
docker compose up runs all three services: the frontend on http://localhost:3000, the
backend on http://localhost:8000, and PostgreSQL. Inside the compose network the frontend
reaches the API at http://backend:8000 and the backend reaches the database at host db,
since containers don't share the host's loopback.
Notes persistence is wired end to end. Be aware of the following before picking up work:
- The frontend calls two endpoints that no longer exist.
notegrid.tsxandanalytics.tsxpost to/api/analyze/vocabularyand/api/words/known, which were dropped when the backend was restructured. Vocabulary and quiz features will fail against the current API until these are reimplemented inapp/services/vocab.pyand exposed as routes. Both call sites also hardcodehttp://127.0.0.1:8000from the browser instead of going throughapi.server.ts, so they will not work under Docker and would need CORS. backend/venv/is committed to the repo (~5,800 files) and its interpreter is not portable across machines. Create your own virtualenv as shown above rather than using it. Relatedly, there is no__pycache__entry in.gitignore, so ~2,500 compiled files are tracked.notes2.0/.git.bak/is a committed copy of an old nested git directory.