A full-stack notes app with full CRUD functionality. It's built with React + TypeScript on the frontend, Node.js/Express on the backend, and PostgreSQL (hosted on Neon) for persistence. Originally built as a vanilla JavaScript app, then migrated to a typed React architecture.
- Create, edit, and delete notes with a title, body, and category
- Filter notes by category, with dynamic category-colored UI
- Collapsible/expandable note cards with automatic overflow detection (only shows "View More" when text actually overflows)
- Persistent storage via a PostgreSQL database
- User-facing error handling for failed network requests
- Responsive grid layout
- Frontend: React, TypeScript, Vite, CSS
- Backend: Node.js, Express
- Database: PostgreSQL (Neon, serverless)
- Testing: Vitest, Supertest, React Testing Library
- CI: GitHub Actions
- Security: Helmet, express-rate-limit, Zod
- Deployment: Netlify (frontend), Render (backend)
frontend/ React + TypeScript app (Vite)
src/
components/ Header, FilterBar, Note, NoteModal, NotesContainer, ErrorBanner
context/ NotesContext (shared state, avoids prop drilling)
app.js Express app: middleware and CRUD routes for /notes
server.js Starts the server (kept separate so tests can import app.js)
validation.js Zod schema for note input
config/db.js PostgreSQL connection pool (via pg)
tests/ Backend tests (Vitest + Supertest)
.github/workflows/ CI pipeline (GitHub Actions)
API Endpoints:
| Method | Route | Description |
|---|---|---|
| GET | /notes | Fetch all notes |
| GET | /notes/:id | Fetch a single note (404 if not found) |
| POST | /notes | Create a note (201 on success, 400 on invalid input) |
| PUT | /notes/:id | Update a note (400 on invalid input, 404 if not found) |
| DELETE | /notes/:id | Delete a note (404 if not found) |
Note input is validated with Zod: the title is required (max 100 characters), the category must be one of Personal, Work, Idea, or Journal, and the content is limited to 5000 characters.
Setup:
Create a .env file in the project root:
DATABASE_URL=your_postgres_connection_string
Create the table in your database:
CREATE TABLE notes (
id VARCHAR NOT NULL PRIMARY KEY,
notetitle VARCHAR,
notecategory VARCHAR,
notecontent TEXT
);The frontend reads its API address from VITE_API_URL and falls back to http://localhost:3000 if it isn't set.
Backend:
npm install
npm run devFrontend:
cd frontend
npm install
npm run dev- Backend (18 tests): Vitest + Supertest cover all five routes, including failure cases (missing title, unknown ID, invalid category, oversized input), the security headers Helmet sets, and the rate limiter's 429 behavior (tested on a small standalone Express app).
- Frontend (11 tests): Vitest + React Testing Library cover
ErrorBanner,FilterBar, and theNotecard's "View More" overflow logic. - Backend tests run against a separate
notes_testPostgres database, never the production database.
# Backend (from the project root)
npm test
# Frontend
cd frontend
npm testThe backend tests use TEST_DATABASE_URL if it's set, and otherwise fall back to a local Postgres database named notes_test.
To create the local test database:
psql -c "CREATE DATABASE notes_test;"
psql notes_test -c "CREATE TABLE notes (id VARCHAR NOT NULL PRIMARY KEY, notetitle VARCHAR, notecategory VARCHAR, notecontent TEXT);"A GitHub Actions workflow runs on every push to main and on every pull request, with two jobs:
- backend: starts a Postgres service container, creates the schema, and runs the backend tests
- frontend: runs the frontend tests, type-checks, and builds the app
- Render deploys the backend only after CI checks pass
- Merges to
mainrequire thebackendandfrontendchecks to pass
- Helmet sets standard security headers
- Rate limiting: 100 requests per IP per 15 minutes
- Input validation with Zod on create and update
- Parameterized queries for all database access
- CORS restricted to an allowlist of origins
- Git history scanned with gitleaks (no findings)
- add message confirming if you would like to delete a card
- add auth to the project so database can be unique for each viewer
- add markdown or better formatting to the notes so bullets and headings can be featured
