An AI-native notepad that thinks with you.
NextGenNote isn't just a place to store text — it's a workspace that turns raw emotion into reflective writing, links scattered ideas into a visual graph of thought, and organizes your notes the way your brain actually works: by mood, by collection, and by connection.
- Overview
- Feature Walkthrough
- Tech Stack
- System Architecture
- Core Algorithms
- Project Structure
- Database Schema
- API Reference
- Environment Variables
- Local Setup
- Deployment
- Responsive Design Notes
- Roadmap
NextGenNote was built from scratch as a full-stack MERN application (MongoDB, Express, React, Node.js), designed to answer one question: what would a notepad look like if it were built in the AI era instead of the 1990s?
The result is a note-taking app with four pillars that don't typically coexist in the same product:
- A real, working AI co-writer (AI Canvas) that turns a described emotion into a genuine reflective journal entry — not a gimmick, an actual Groq-powered LLM call with a carefully engineered system prompt
- A knowledge graph (Connecting Thoughts) that visualizes how your notes relate to each other, using a custom-built force-directed physics simulation — no external graph library
- Organizational depth — collections with custom colors, pinning, archiving, soft-delete trash with recovery, and full-text search — all built on top of a single flexible Note schema
- Developers Mode where you can store and analyze your code properly you could convert it to your own respective language along with learning through workflows and commands.
- A production-grade auth & UX layer — JWT sessions with real expiry checking, multi-account switching on one device, dark mode, customizable greetings/avatars, and a fully responsive layout that adapts from a 4K desktop down to a 360px phone screen with a native-feeling bottom tab bar
- Installable PWA — a real web app manifest, custom app icons, and an auto-updating service worker (via
vite-plugin-pwa) mean NextGenNote can be installed to a home screen or desktop and launches like a native app, with static assets cached for offline resilience
The first thing a new, logged-out visitor sees. A marketing-style hero section explaining the product, a feature checklist (Free/Open Source, Graph Builder, Custom Collections, AI Mode), a "Get Started Free" CTA, and a dashboard mockup preview.
Glassmorphic split-screen Login and Signup pages, each with a dark animated brand panel on the left (desktop only) and a frosted-glass form card on the right. Sessions persist for 7 days via JWT; after that, the token is detected as expired on load and the user is transparently returned to the Landing page rather than seeing a broken dashboard.
The home base. A collapsible sidebar (desktop) or bottom tab bar (mobile) for navigation between All Notes, Starred, Archive, Trash, and custom Collections. Notes are split into Pinned and Other Notes sections, with a live search bar, a grid/bar layout toggle, and a 2/3/4/5-column density toggle for grid view on wide screens — both preferences persist per-browser via localStorage.
A built-in, VS Code–styled code editor and snippet manager (/devmode), separate from the note-taking experience. Snippets are stored per-user in their own CodeSnippet collection, with a file-tree sidebar, an inline-renameable tab, line numbers, and a live cursor-position/line-count status bar. Three Groq-powered actions run server-side on top of whatever code is in the editor:
- Auto language detection — debounced ~1s after typing stops, so it doesn't fire a model call on every keystroke
- Convert — translates the current snippet into another supported language, shown side-by-side before you choose to apply it
- Diagram — generates a Mermaid.js logic diagram summarizing the code's control flow, rendered inline in a modal
Notes open in view mode by default (read-only) and only become editable after explicitly clicking Edit — this prevents accidental edits from a stray click. From the editor, a note can be saved, archived, linked to other notes, or assigned to a collection. New notes are drafts until the user actually writes something and saves — nothing is persisted to the database on a blank "New Note" click.
The signature feature. A togglable panel (side panel on desktop, full-screen sheet on mobile/tablet) where the user picks a mood chip and describes what's on their mind. That prompt is sent to a Groq-hosted Llama 3.3 70B model with a system prompt engineered to produce a warm, human-sounding reflective journal entry — not a clinical AI summary. The generated text can be saved directly as a note, tagged with the mood and an "AI Generated" badge.
User-created groupings (e.g., "DSA," "Mood," "Reading List") each with a custom accent color. Notes inside a collection render with a colored top border matching that collection. Notes can be created directly inside a collection, or added to one after the fact via an "Add existing note" picker.
An Obsidian-style knowledge graph. Every note is a node; explicit links between notes (created from inside the editor) are edges. Nodes glow more intensely the more connections they have ("hub" notes), and the whole graph is rendered as an interactive, draggable force-directed simulation built entirely in raw SVG + requestAnimationFrame — no D3, no graph library. Includes filters for "Strong Links" (2+ connections) and "Orphan Nodes" (zero connections), plus live stats (Total Notes, Connections, Clusters).
A real-time-feeling activity log: "First note created!", "You pinned a note!", "You archived a note!", etc. — triggered server-side any time a relevant action occurs, with per-item delete and a "Clear all" action, respecting a user-level notification on/off toggle in Settings.
Dark/light mode toggle, notification toggle, a 7-avatar picker, a customizable dashboard greeting (default "Hola"), and a full Profile page with editable display name, account verification badge, and multi-account switching — the app remembers every account a user has logged into on that device and lets them swap between them without re-entering a password.
Below the sm breakpoint, the sidebar disappears entirely in favor of a bottom tab bar (Notes / AI / Alerts / More), with a slide-up sheet for secondary navigation (Starred/Archive/Trash, Workspace, Connecting Thoughts, Developer Mode, Settings). Each collection row in the Workspace list has its own delete control on mobile, matching what's available on the desktop sidebar. The AI Canvas becomes a full-screen sheet through tablet widths. The Connecting Thoughts graph gets a collapsible filter drawer instead of a fixed side panel, and node-dragging supports real touch events, not just mouse.
| Layer | Technology |
|---|---|
| Frontend framework | React (Vite) |
| Styling | Tailwind CSS v4 |
| Icons | lucide-react |
| Routing | React Router v6 |
| HTTP client | Axios (with request/response interceptors) |
| Backend framework | Express.js |
| Database | MongoDB Atlas + Mongoose ODM |
| Authentication | JWT (jsonwebtoken) + bcrypt password hashing |
| AI provider | Groq API (Llama 3.3 70B Versatile) |
| Frontend hosting | Vercel |
| Backend hosting | Render |
┌─────────────────────┐ HTTPS / JSON ┌──────────────────────┐
│ React SPA │ ─────────────────────────────▶│ Express API │
│ (Vercel) │ │ (Render) │
│ │ ◀───────────────────────────── │ │
│ - AuthContext │ JWT in Authorization │ - authMiddleware │
│ - ThemeContext │ header on every call │ (verifies JWT) │
│ - axios interceptors │ │ - Controllers │
│ (attach token, │ │ (auth, note, │
│ handle 401) │ │ collection, user, │
└──────────┬───────────┘ │ notification, ai) │
│ └──────────┬───────────┘
│ localStorage │
│ (token, user, │ Mongoose
│ saved accounts, │
│ recent notes, ▼
│ grid density) ┌──────────────────────┐
│ │ MongoDB Atlas │
│ │ │
│ │ Collections: │
│ │ - users │
│ │ - notes │
│ │ - collections │
│ │ - notifications │
│ └──────────────────────┘
│
│ external call (server-side only)
▼
┌──────────────────────┐
│ Groq API │
│ (Llama 3.3 70B) │
│ — AI Canvas note │
│ generation │
└──────────────────────┘
Request flow for a typical authenticated action (e.g., pinning a note):
- User clicks "Pin" in
NoteCard→Dashboard.handlePin(note)fires togglePinNote(note._id)calls the sharedapiaxios instance- Axios request interceptor reads
tokenfromlocalStorageand attaches it asAuthorization: Bearer <token> - Request hits Express →
authMiddlewareverifies the JWT signature and expiry, decodesuserId, attaches it toreq noteController.togglePinflipsisPinned, saves via Mongoose, and callsnotify()to log a notification (respecting the user'snotificationsEnabledpreference)- Response returns → frontend calls
refresh(), which re-fetches notes, counts, and notifications in parallel
The knowledge graph is not powered by any charting/graph library — it's a hand-rolled physics simulation running on every animation frame:
- Repulsion: every pair of nodes exerts an inverse-square repulsive force on each other (
force = k / distance²), so nodes naturally spread out and never stack on top of each other - Spring attraction: every edge (link between two notes) acts like a spring with a target rest-length of 150px — nodes connected by an edge are pulled toward that distance, neither collapsing together nor drifting apart indefinitely
- Centering force: a weak force pulls every node gently toward the center of the canvas, preventing the whole graph from drifting off-screen over time
- Damping: velocity is multiplied by 0.85 every frame, acting as friction so the simulation settles into a stable layout instead of oscillating forever
- Dragging override: while a node is actively being dragged (mouse or touch), physics forces are suspended for that node and its position is set directly from pointer coordinates; releasing it re-enables physics
This runs inside a useEffect + requestAnimationFrame loop, recalculating all node positions ~60 times per second while the graph page is open.
Cluster detection uses a simple graph traversal (iterative DFS via an explicit stack, to avoid recursion depth issues): starting from each unvisited node, it walks all reachable neighbors via the adjacency list built from edges, marking them visited, and increments a cluster counter each time it has to start a new, unvisited component. This is the standard "count connected components" algorithm.
Rather than trusting localStorage blindly, the app decodes the JWT's exp claim client-side on every app load (jwt-decode) and compares it against the current time before rendering any route. If expired, the token is purged immediately and the user is treated as logged-out — landing on the public Landing page rather than a broken authenticated view. A secondary axios response interceptor catches any 401 that slips through (e.g., server-side revocation or clock drift) as defense-in-depth.
The sidebar's "Recent Notes" list is a simple bounded most-recently-used cache implemented directly in React state + localStorage: opening a note removes any existing entry for that note ID, unshifts it to the front, and slices the array to a maximum of 5 entries — giving O(n) insert on a tiny fixed-size list, which is effectively constant time in practice.
Rather than maintaining separate database collections per view (Starred/Archive/Trash/Collection), every note carries boolean flags (isPinned, isArchived, isDeleted) and an optional collectionId. A single getNotes endpoint builds a different Mongoose query filter object depending on the requested view — this keeps the schema simple and avoids data duplication or complex joins, at the cost of slightly more logic in the query-builder function.
The Groq call uses a fixed system prompt instructing the model to act as "a thoughtful journaling assistant" that converts a described feeling into a 2-4 paragraph reflective note in the user's own voice — explicitly told to avoid titles, greetings, or sign-offs, and to avoid sounding clinical or overly poetic. The user's selected mood chip (if any) is prepended to their free-text prompt before being sent, giving the model extra context without requiring the user to type it out themselves.
NextGenNote/
├── backend/
│ ├── config/
│ │ └── db.js # (optional) Mongo connection helper
│ ├── controllers/
│ │ ├── authController.js # register, login, googleLogin
│ │ ├── noteController.js # CRUD, pin/archive/trash, graph data, linking
│ │ ├── collectionController.js # create/list/delete collections
│ │ ├── notificationController.js # list/delete/clear notifications
│ │ ├── userController.js # profile + settings (avatar, theme, greeting)
│ │ ├── aiController.js # Groq API call for AI Canvas
│ │ └── devModeController.js # snippet CRUD, language detect, convert, diagram
│ ├── middleware/
│ │ └── authMiddleware.js # JWT verification, attaches req.userId
│ ├── models/
│ │ ├── User.js
│ │ ├── Note.js
│ │ ├── Collection.js
│ │ ├── Notification.js
│ │ └── CodeSnippet.js # Developer Mode snippets
│ ├── routes/
│ │ ├── authRoutes.js
│ │ ├── noteRoutes.js
│ │ ├── collectionRoutes.js
│ │ ├── notificationRoutes.js
│ │ ├── userRoutes.js
│ │ ├── aiRoutes.js
│ │ └── devModeRoutes.js
│ ├── utils/
│ │ └── notify.js # shared notification-creation helper
│ ├── .env # MONGO_URI, JWT_SECRET, GROQ_API_KEY, PORT
│ ├── package.json
│ └── server.js # Express app entrypoint, incl. GET /health
│
├── .github/
│ └── workflows/
│ └── keep-alive.yml # pings /health every 14 min to prevent Render cold starts
│
├── frontend/
│ ├── src/
│ │ ├── api/
│ │ │ ├── axios.js # shared instance + interceptors
│ │ │ ├── notes.js
│ │ │ ├── collections.js
│ │ │ ├── notifications.js
│ │ │ ├── user.js
│ │ │ ├── ai.js
│ │ │ └── devmode.js
│ │ ├── components/
│ │ │ ├── Sidebar.jsx
│ │ │ ├── MobileTabBar.jsx # bottom nav + "More" sheet (Developer Mode, delete collection, etc.)
│ │ │ ├── NoteCard.jsx # grid view
│ │ │ ├── NoteBar.jsx # bar/list view
│ │ │ ├── NewNoteCard.jsx
│ │ │ ├── AICanvas.jsx
│ │ │ ├── BlockEditor.jsx # rich block-based note editing
│ │ │ ├── BlockViewer.jsx # read-only block rendering
│ │ │ ├── MermaidDiagram.jsx # renders Developer Mode's generated diagrams
│ │ │ ├── ProfileMenu.jsx
│ │ │ ├── NotificationPanel.jsx
│ │ │ ├── ConfirmDialog.jsx
│ │ │ ├── CollectionModal.jsx
│ │ │ ├── AddNotesToCollectionModal.jsx
│ │ │ └── ProtectedRoute.jsx
│ │ ├── context/
│ │ │ ├── AuthContext.jsx / AuthContextStore.js
│ │ │ └── ThemeContext.jsx / ThemeContextStore.js
│ │ ├── constants/
│ │ │ └── avatars.js
│ │ ├── utils/
│ │ │ ├── accounts.js # multi-account localStorage helpers
│ │ │ └── tokenValid.js # JWT expiry check
│ │ ├── pages/
│ │ │ ├── Landing.jsx
│ │ │ ├── Login.jsx
│ │ │ ├── Signup.jsx
│ │ │ ├── Dashboard.jsx
│ │ │ ├── NoteEditor.jsx
│ │ │ ├── Settings.jsx
│ │ │ ├── ProfileSettings.jsx
│ │ │ ├── ConnectingThoughts.jsx
│ │ │ └── DevMode.jsx
│ │ ├── App.jsx # route definitions
│ │ ├── main.jsx
│ │ └── index.css # Tailwind entry
│ ├── .env / .env.production # VITE_API_URL
│ ├── vite.config.js
│ └── package.json
│
└── README.md
User
name, email (unique), password (hashed, optional for OAuth), googleId,
avatar, darkMode, notificationsEnabled, dashboardGreeting, timestamps
Note
userId (ref User), title, content, isAIGenerated, mood,
isPinned, isArchived, isDeleted, collectionId (ref Collection),
linkedNotes [ref Note], timestamps
Collection
userId (ref User), name, color, timestamps
Notification
userId (ref User), message, timestamps
CodeSnippet
userId (ref User), title (default "Untitled Snippet"), code,
language (default "plaintext"), timestamps
| Method | Route | Description |
|---|---|---|
| POST | /api/auth/register |
Create account |
| POST | /api/auth/login |
Email/password login |
| POST | /api/auth/google |
Google OAuth login |
| GET | /api/notes |
List notes (supports ?filter= or ?collectionId=) |
| GET | /api/notes/counts |
Starred/archive/trash counts |
| GET | /api/notes/graph/data |
Nodes + edges for Connecting Thoughts |
| POST | /api/notes |
Create note |
| GET | /api/notes/:id |
Get single note |
| PUT | /api/notes/:id |
Update title/content |
| PATCH | /api/notes/:id/pin |
Toggle pin |
| PATCH | /api/notes/:id/archive |
Archive note |
| PATCH | /api/notes/:id/restore |
Restore from archive/trash |
| PATCH | /api/notes/:id/collection |
Assign/remove collection |
| PATCH | /api/notes/:id/link |
Toggle link to another note |
| DELETE | /api/notes/:id |
Move to trash |
| DELETE | /api/notes/:id/permanent |
Permanently delete |
| DELETE | /api/notes/trash/empty |
Empty trash |
| GET / POST / DELETE | /api/collections |
List / create / delete collections |
| GET | /api/notifications |
List notifications |
| DELETE | /api/notifications/:id |
Delete one |
| DELETE | /api/notifications/clear |
Clear all |
| GET | /api/user/me |
Get profile + settings |
| PATCH | /api/user/settings |
Update avatar/theme/greeting/notifications |
| POST | /api/ai/generate |
Generate an AI note from a mood + prompt |
| GET | /api/devmode/languages |
List supported languages for Developer Mode |
| GET / POST | /api/devmode |
List / create code snippets |
| GET / PUT / DELETE | /api/devmode/:id |
Get / update / delete a snippet |
| POST | /api/devmode/detect-language |
Auto-detect the language of a code snippet |
| POST | /api/devmode/translate |
Convert a snippet from one language to another |
| POST | /api/devmode/diagram |
Generate a Mermaid logic diagram from a snippet |
| GET | /health |
Lightweight liveness check, no DB call — used by the keep-alive workflow (see below) |
| GET | / |
Basic "API is running" check |
All routes except /auth/*, /health, and / require a valid Authorization: Bearer <token> header.
backend/.env
PORT=5000
MONGO_URI=mongodb+srv://<user>:<pass>@cluster0.xxxxx.mongodb.net/nextgennote
JWT_SECRET=your_secret_key
GROQ_API_KEY=your_groq_key
GOOGLE_CLIENT_ID=your_google_client_id # optional, if OAuth is enabled
frontend/.env / .env.production
VITE_API_URL=http://localhost:5000/api # local
VITE_API_URL=https://your-backend.onrender.com/api # production
VITE_GOOGLE_CLIENT_ID=your_google_client_id # optional
GitHub repository secret (Settings → Secrets and variables → Actions, not a .env file)
RENDER_BACKEND_URL=https://your-backend.onrender.com # used by .github/workflows/keep-alive.yml
# Backend
cd backend
npm install
npm run dev # http://localhost:5000
# Frontend (separate terminal)
cd frontend
npm install
npm run dev # http://localhost:5173- Backend: deployed on Render as a Node web service, root directory
backend, build commandnpm install, start commandnpm start. Environment variables set directly in the Render dashboard. - Frontend: deployed on Vercel, root directory
frontend, framework preset Vite, build commandnpm run build, output directorydist.VITE_API_URLpointed at the live Render backend URL. - CORS on the backend is configured to accept requests from the deployed Vercel origin.
Render's free instance type spins down after 15 minutes of inactivity, which otherwise means the first request after idle can take 30-50+ seconds to respond. To avoid that without paying for an always-on plan:
GET /healthon the backend is a lightweight, DB-free liveness check.github/workflows/keep-alive.ymlruns on a GitHub Actions schedule (*/14 * * * *) and hits that endpoint, keeping the instance from ever going idle long enough to sleep- The ping retries up to 3 times with a 60-second timeout per attempt, so an occasional cold wake (if a scheduled run is ever delayed past the 15-minute window) still succeeds instead of reporting a false failure
- Requires a
RENDER_BACKEND_URLrepository secret (Settings → Secrets and variables → Actions) set to the live Render URL, e.g.https://nextgennote-3.onrender.com - GitHub automatically disables scheduled workflows after 60 days with no commits to the repo — if pings ever silently stop, check the Actions tab and re-enable it
The app was built desktop-first and then systematically retrofitted for mobile/tablet:
- Sidebar is hidden entirely below the
smbreakpoint, replaced by a fixed bottom tab bar (Notes / AI / Alerts / More) with a slide-up sheet for secondary navigation - AI Canvas is a docked 320px side panel on large desktop screens (
lg:and up) but becomes a full-screen sheet with a dismissible backdrop on everything below that, including tablets — this avoids the panel fighting the sidebar for space at in-between widths - Connecting Thoughts graph shrinks its rendered height progressively across breakpoints and gains real touch-event support (
onTouchStart/Move/End) for dragging nodes on phones/tablets, not just mouse - Grid density control (3/4/5 columns) is desktop-only (
lg:and up) and persisted per-browser vialocalStorage, since it has no meaningful effect below that width
Planned but not yet built:
- Mood timeline / emotional insight charts
- AI-generated weekly digest summarizing the week's notes
- "On this day" note resurfacing
- Voice-to-note via the browser's native Speech Recognition API
- Daily writing streak tracking
Built as a hands-on learning project covering full-stack architecture, JWT authentication, MongoDB schema design, custom physics-based data visualization, and LLM Integration.