Skip to content

NJstudios/notes-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Notes App

A modular note-taking app with:

  • Dashboard to create, open, and delete notes
  • Per-note page with modular “blocks”:
    • Text blocks
    • Todo list blocks
    • Table blocks (with add/delete rows and columns)
  • Each block can:
    • Have a custom title
    • Be collapsed/expanded
    • Be full-width or half-width in the grid

Stack:

  • Frontend: Next.js (App Router) + TypeScript + shadcn/ui
  • Backend: FastAPI + SQLAlchemy (async) + PostgreSQL (Neon)
  • DB: JSONB data field on blocks to support flexible layouts

Project Structure

notes-app/
  backend/
    main.py         # FastAPI app: models, schemas, routes
    .env            # Backend env vars (DATABASE_URL, DEV_USER_ID)
    .venv/          # Python virtualenv (local only)
  frontend/
    app/            # Next.js app routes (/ and /notes/[id])
    components/
      blocks/       # BlockCard, TextBlockBody, TodoBlockBody, TableBlockBody
      ui/           # shadcn/ui components
    lib/api.ts      # Frontend API client → FastAPI
    .env.local      # NEXT_PUBLIC_API_BASE
Backend Setup (FastAPI + Postgres)
1. Create and activate virtualenv
From notes-app/backend:

bash
Copy code
# Windows PowerShell
python -m venv .venv
.\.venv\Scripts\activate

# Mac/Linux (for reference)
# python -m venv .venv
# source .venv/bin/activate
2. Install dependencies
bash
Copy code
pip install fastapi uvicorn[standard] sqlalchemy[asyncio] asyncpg python-dotenv
3. Environment variables
Create notes-app/backend/.env:

env
Copy code
DATABASE_URL=postgresql+asyncpg://<user>:<password>@<host>/<db_name>
DEV_USER_ID=00000000-0000-0000-0000-000000000001
DATABASE_URL should be your Neon (or other Postgres) connection string, but with postgresql+asyncpg://....

DEV_USER_ID is a fixed UUID used as a fake “current user” during development.

4. Create tables (one-time bootstrap)
In backend/main.py, there is a startup hook. For the first run, uncomment the table creation part:

python
Copy code
@app.on_event("startup")
async def on_startup():
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
Then run:

bash
Copy code
uvicorn main:app --reload --port 8000
Once the app starts successfully and tables are created, comment that block back out so you don’t accidentally auto-mutate schema later.

5. Run backend
From notes-app/backend:

bash
Copy code
.\.venv\Scripts\activate   # if not already active
uvicorn main:app --reload --port 8000
FastAPI docs: http://127.0.0.1:8000/docs

API root: http://127.0.0.1:8000

Frontend Setup (Next.js + shadcn/ui)
1. Install dependencies
From notes-app/frontend:

bash
Copy code
npm install
Make sure you’re using a reasonably recent Node LTS.

2. Frontend env
Create notes-app/frontend/.env.local:

env
Copy code
NEXT_PUBLIC_API_BASE=http://localhost:8000
This is used by lib/api.ts to talk to your FastAPI backend.

3. Run dev server
From notes-app/frontend:

bash
Copy code
npm run dev
App will be at:
http://localhost:3000

Frontend API Client (lib/api.ts)
The frontend talks to the backend via a small typed wrapper:

listNotes() → GET /notes

createNote(title) → POST /notes

deleteNote(id) → DELETE /notes/{id}

getNote(id) → GET /notes/{id}

createBlock(noteId, type, position, data) → POST /notes/{noteId}/blocks

updateBlock(blockId, data) → PATCH /blocks/{blockId}

deleteBlock(blockId) → DELETE /blocks/{blockId}

Block.data is a free-form JSON object where we store:

title – block title

collapsed – boolean

layout.size – "full" or "half"

Table-specific keys:

columns: string[]

rows: string[][]

Todo-specific keys:

items: { text: string; done: boolean }[]

Text-specific keys:

text: string

UI / Features
Dashboard (Home: /)
Shows list of notes.

Each note:

Title

Created/updated times (optional in UI)

Open button → /notes/[id]

Delete button → calls DELETE /notes/{id} with optimistic UI update.

“New note” button:

POST /notes

Backend also creates a default empty text block.

Note Page (/notes/[id])
Uses the loaded Note object from GET /notes/{id} which includes blocks: Block[].

Block Types
Text Block

Basic text area (or input) storing data.text.

Todo Block

List of todo items stored in data.items: { text, done }[].

Can add items, toggle them done/undone, and edit text.

Table Block

Uses data.columns and data.rows.

Columns:

Editable header names.

Add column button.

Delete column button (can’t delete the last column).

Rows:

Add row button.

Delete row icon per row.

Cells editable via <Input>.

Shared Block Behavior
Implemented in BlockCard:

Title per block

Editable Input bound to block.data.title.

Fallback label based on block type (Text/Todo/Table).

Collapse/Expand

data.collapsed boolean.

When collapsed, only header (title + controls) is shown.

Layout width

data.layout.size = "full" or "half".

Mapped to grid classes in the note page:

"full" → md:col-span-2

"half" → md:col-span-1

Delete block

onDelete calls DELETE /blocks/{id} with optimistic removal.

Performance / Saving
Editing a block uses optimistic updates + debounce:

Local state is updated immediately.

A setTimeout (~400ms) schedules a PATCH /blocks/{id}.

If more edits happen before the timer fires, the old timer is cleared, and a new one is scheduled.

This avoids spamming the backend on every keystroke.

API Overview (Backend)
Base URL: http://localhost:8000

Notes
GET /notes
Returns all notes for DEV_USER_ID. Each note can be returned without blocks or with blocks depending on your implementation; in this app, the dashboard usually only needs basic note metadata.

POST /notes
Creates a note and a default empty text block.

Request body:

json
Copy code
{ "title": "My note title" }
Response body:

json
Copy code
{
  "id": "...",
  "title": "My note title",
  "created_at": "...",
  "updated_at": "...",
  "blocks": [
    {
      "id": "...",
      "type": "text",
      "position": 0,
      "data": { "text": "" }
    }
  ]
}
GET /notes/{note_id}
Returns a single note with its blocks.

DELETE /notes/{note_id}
Deletes the note and associated blocks (via explicit delete on blocks with note_id, or via ON DELETE CASCADE).

Blocks
POST /notes/{note_id}/blocks
Creates a block for a specific note. Frontend sends type, position, and initial data.

PATCH /blocks/{block_id}
Replaces the block’s data JSON with whatever the frontend sends, e.g.:

json
Copy code
{ "data": { "title": "Todos", "items": [] } }
DELETE /blocks/{block_id}
Deletes the block.

Development / Debugging Tips
Backend:

Watch the uvicorn console for stack traces.

500 errors usually mean DB issues (connection, schema, or async usage).

Frontend:

Check browser devtools console.

Check npm run dev terminal for TypeScript/Next.js errors.

If you change the DB schema in early development:

Easiest is often to DROP and re-run Base.metadata.create_all.

For a real project, you’d add Alembic migrations later.

Future Enhancements
Real user auth (instead of hard-coded DEV_USER_ID).

Additional block types (calendar, kanban, code snippet, embedded links).

Drag-and-drop block reordering.

Full-text search over note content.

Collaboration / sharing (multi-user notes).

About

A website using next.js + neon db to help store notes + tables with useful information.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors