Skip to content

gusanchefullstack/fsdev-bookmark-manager-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bookmark Manager App

A full-stack bookmark manager built as a Frontend Mentor challenge — save, organize, search, filter, and sort your bookmarks behind real user accounts, not just a static mock of one.

Tests License Node

Table of contents

Overview

The challenge

Users can:

  • Add new bookmarks with a title, description, website URL, and tags
  • View all their bookmarks, with favicon, title, URL, description, tags, view count, last visited date, and date added
  • Search for bookmarks by title
  • Filter bookmarks by one or multiple tags from the sidebar, and reset filters
  • View and manage archived bookmarks separately from the main view
  • Archive/unarchive bookmarks without deleting them
  • Pin/unpin bookmarks to keep important ones easily accessible
  • Edit existing bookmarks
  • Copy bookmark URLs to the clipboard
  • Visit bookmarked websites directly from the app
  • Sort bookmarks by "Recently added", "Recently visited", or "Most visited"
  • Toggle between light and dark color themes
  • Sign up, log in, log out, and reset a forgotten password by email
  • Use the app at the optimal layout for their device, with visible hover/focus states throughout

Screenshots

Desktop (1440px)

Desktop screenshot of the bookmark manager home screen showing the sidebar, bookmark cards, and header

Mobile (375px)

Mobile screenshot of the bookmark manager home screen with the collapsed header and single-column bookmark cards

Links

Architecture

This was built as a full-stack app rather than a static frontend reading data.json — real accounts, real JWT auth, and a real Postgres database — as a deliberate step up from the challenge's baseline scope.

apps/
├── frontend/   React 19 + TypeScript + Vite, CSS Modules, React Router
└── backend/    Express + TypeScript + Prisma (Postgres), JWT auth, Resend email
  • Auth: signup/login issue a JWT in an httpOnly cookie; bcrypt hashes passwords; forgot/reset password generates a time-limited token and emails a reset link via Resend.
  • Data seeding: on first signup, each user's account is seeded with the bookmarks from data.json, so every account starts populated — matching the challenge's "populate content on first load" behavior, just per-user instead of shared/static.
  • Favicons: seeded bookmarks reuse the provided local favicon assets; bookmarks added afterward get their favicon fetched automatically from the site's domain.

Project structure

apps/backend/src/
├── routes/       auth.ts (signup/login/logout/me/forgot/reset), bookmarks.ts (CRUD, search, sort, tags)
├── middleware/   JWT auth guard
├── lib/          prisma client, jwt signing, favicon lookup, Resend email, data.json seeding
└── prisma/       schema.prisma + migrations

apps/frontend/src/
├── pages/        SignIn/SignUp/ForgotPassword/ResetPassword, HomePage (active + archived views)
├── components/   Header, Sidebar, BookmarkCard/Grid, AddEditBookmarkModal, ConfirmDialog, ActionsMenu, ProfileMenu
├── context/      AuthContext, ThemeContext, BookmarksContext
└── styles/       design tokens (variables.css) pulled from the Figma design system

Built with

Getting started

Prerequisites

  • Node.js >= 20
  • A PostgreSQL database (local via Docker, or a Neon project)
  • A Resend API key (optional in local dev — without it, reset links are logged to the server console instead of emailed)

Installation

git clone https://github.com/gusanchefullstack/fsdev-bookmark-manager-app.git
cd fsdev-bookmark-manager-app
npm install

Environment variables

Copy the example env files and fill them in:

cp apps/backend/.env.example apps/backend/.env
cp apps/frontend/.env.example apps/frontend/.env

apps/backend/.env

Variable Description Required Default
DATABASE_URL Postgres connection string Yes
JWT_SECRET Secret used to sign session JWTs Yes
RESEND_API_KEY Resend API key for password-reset emails No logs the reset link instead
FRONTEND_URL Frontend origin, for CORS + reset links No http://localhost:5173
PORT API server port No 4000

apps/frontend/.env

Variable Description Required Default
VITE_API_URL Base URL the frontend calls for the API No /api

Running the app

# apply the Prisma schema to your database
npm run dev -w apps/backend -- --run prisma:migrate  # or: cd apps/backend && npx prisma migrate dev

# in one terminal
npm run dev:backend

# in another terminal
npm run dev:frontend

The frontend runs at http://localhost:5173 and proxies /api to the backend at http://localhost:4000.

API reference

All /api/bookmarks routes require an authenticated session (the bm_token httpOnly cookie set by signup/login).

POST /api/auth/signup

{ "name": "Ada Lovelace", "email": "ada@example.com", "password": "supersecret" }

Response 201:

{ "user": { "id": "", "email": "ada@example.com", "name": "Ada Lovelace", "avatarUrl": null } }

GET /api/bookmarks?search=react&tags=Framework&archived=false&sort=most-visited

Response 200:

{ "bookmarks": [{ "id": "", "title": "React Docs", "url": "https://react.dev", "tags": ["JavaScript", "Framework"], "pinned": false, "visitCount": 12 }] }

POST /api/bookmarks

{ "title": "Vitest", "description": "A blazing fast unit test framework.", "url": "https://vitest.dev", "tags": ["Testing", "Tools"] }

PATCH /api/bookmarks/:id

Accepts any subset of title, description, url, tags, pinned, isArchived.

DELETE /api/bookmarks/:id · POST /api/bookmarks/:id/visit

Permanently deletes a bookmark, or records a visit (increments visitCount, sets lastVisited).

Tests

npm run test:backend    # 15 vitest tests: auth + bookmarks routes against an in-memory fake Prisma client
npm run test:frontend   # 16 vitest + React Testing Library tests: components, hooks, and utils
npm test                # both

My process

What I learned

  • Design tokens from Figma, not hardcoded values. Pulling get_variable_defs from the Figma Desktop MCP gave a concrete color/spacing/typography scale to translate into CSS custom properties (apps/frontend/src/styles/variables.css) up front, instead of eyeballing colors from screenshots later.
  • Seed data has to travel with the deployed backend. The first version of the seeding logic read data.json from the repo root via a relative filesystem path — fine locally, but it would have broken the moment the backend ran as a Vercel serverless function with a different working directory. Importing the seed data as a JSON module from inside src/ instead makes it part of the bundle.
  • jsdom needs a real origin before localStorage exists. Vitest's jsdom environment doesn't expose window.localStorage unless you give it an explicit http(s) URL (and even then, Node's own experimental global localStorage can shadow it) — worth a small polyfill in the test setup file rather than a surprise failure per test file.

Continued development

  • Wire up real-time favicon refresh (currently only re-fetched when a bookmark's URL changes)
  • Add optimistic UI updates for pin/archive so the card animates before the network round-trip resolves
  • Expand the Vercel deployment to attach a custom domain and add uptime monitoring

Useful resources

  • Prisma with PostgreSQL guide — used this to get the schema and migration workflow set up correctly against Neon.
  • React Router v6 data APIs — clarified how nested <Routes> inside a route element resolve paths, which is what makes the / vs /archived split inside the authenticated app shell work.

AI collaboration

This project was built with Claude Code as a pair-programming assistant:

  • Planning: Claude explored the Figma file via the Figma Desktop MCP (design system tokens, screen layouts across breakpoints and states) and the challenge's data.json/assets before proposing an implementation plan, which was reviewed and approved before any code was written.
  • Implementation: Claude wrote the monorepo scaffold, backend API, and all frontend components/tests, verifying each responsive breakpoint and CRUD flow directly in a real browser (and against a local Postgres instance) rather than relying on typechecking alone.
  • What worked well: catching a real deployment bug during manual verification (the seed-data file path issue above) before it ever reached production, and getting pixel-accurate screenshots by scripting a headless Chrome session instead of relying on visual approximation.
  • What didn't: browser-automation tooling for resizing the viewport to exact breakpoint widths was unreliable mid-session; falling back to a small Puppeteer script against a locally installed Chrome binary was more dependable for the final 375px/1440px screenshots.

Roadmap

  • Auth (signup/login/logout, forgot/reset password)
  • Bookmark CRUD, search, sort, tag filter, archive, pin
  • Responsive layout (mobile/tablet/desktop) matching Figma
  • Light/dark theme toggle
  • Backend + frontend test suites
  • Deploy to Vercel with Neon
  • Configure Resend for production password-reset emails
  • Submit to Frontend Mentor

License

This project is for personal portfolio and learning purposes as part of a Frontend Mentor challenge.

Author

LinkedIn GitHub Hashnode X Bluesky freeCodeCamp Frontend Mentor

About

Full-stack Bookmark Manager app (Frontend Mentor challenge) — React 19 + Vite frontend, Express + Prisma + Postgres backend, JWT auth

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages