Bookmark manager

Markit is a web app for saving and organizing links. You paste an address, it fills in the page's title, description and image on its own, and files everything into collections you can reach from any browser. I designed the interface, built the frontend in React and the API in FastAPI, and took it to production.
Saved links tend to end up in forgotten tabs, loose notes or bookmarks locked to a single browser. One rule guided the project: saving should cost one gesture, and finding a link later should cost one more.
How it works:
1- Paste the link. If the https:// is missing, the app adds it.

2- Review the preview. Title, description, image and favicon arrive filled in, and you can tweak everything before saving.

3- Organize and find. Collections, a recents view, text search, and filters for sort order, domain and the date a link was saved.

Each bookmark opens in a side panel where you can edit the title, description, URL and collection, copy the link or delete it.

Saving without friction. The field takes the raw link and adds the protocol, so pasting and saving is a two-action flow. With the preview already filled in, confirming is the shortest path and editing is optional.
A side panel instead of an edit page. The list stays visible while you edit, so there is no context switch and no lost scroll position.
Filters on demand. The filter bar can be hidden: if you just want to browse, you get a clean screen; if you need to narrow things down, you open sort, domain and date.
The frontend is a static React 19 and TypeScript app built with Vite, using React Router, Tailwind, Iconoir icons and Sonner for notifications. It talks to a FastAPI API on top of SQLAlchemy and SQLite. The metadata scraper uses httpx and BeautifulSoup to pull the title, description, image and favicon from each page.
Because the app makes requests to addresses typed by users, I treated the scraper as the most sensitive part of the system.
Scraper: SSRF protection, so nobody can use the server to reach internal services.
Session: JWT in an HttpOnly cookie, so the browser keeps no token or password that JavaScript can read, and logging out revokes the session.
Accounts: passwords hashed with bcrypt, login attempt limits, and changing the profile or deleting the account requires the current password.
Configuration: the signing key is mandatory and must meet a minimum entropy, CORS is restricted and mutations are protected against CSRF with a header.
The frontend is static and the backend runs on uvicorn behind an Nginx proxy. The whole app is hosted on a single VPS.