Overview
Pagemark API is a lightweight bookmark management service built with Flask. It provides a structured way to save, organize, and search URLs using a combination of tags, collections, and full-text search.
The Problem
As digital information grows, simple browser bookmarks become difficult to manage. Pagemark solves this by providing a centralized API to:
- Organize content beyond simple folders using flexible tagging.
- Automate grouping with "Smart Collections" that update based on rules.
- Search through saved content using an integrated full-text search index.
- Manage Lifecycle with built-in archiving and trash states.
Core Concepts
- Bookmarks: The primary entity representing a saved URL. Each bookmark includes a title, description, and metadata.
- Tags: Flat labels that can be attached to any number of bookmarks. Tags have colors and track their own usage counts.
- Collections: Groups of bookmarks. They come in two flavors:
- Manual: You explicitly add or remove bookmarks.
- Smart: They automatically include bookmarks that match a specific
filter_rule(e.g., a keyword in the title).
- Status: Bookmarks move through a lifecycle:
active(visible),archived(hidden but kept), andtrashed(soft-deleted).
Architecture
The application follows a clean, layered architecture designed for testability and performance:
- Routes: Flask blueprints (e.g.,
app.routes.bookmarks) handle HTTP serialization and status codes. - Service Layer: The
BookmarkService(a singleton) orchestrates business logic, validation, and cross-entity updates. - Search Index: An in-memory
SearchIndexprovides full-text search capabilities using an inverted index. - Repository: The
BookmarkRepositoryabstracts data access. In the current version, this is an in-memory store. - Caching: An internal
LRUCachespeeds up retrieval of frequently accessed bookmarks.
Use Cases
Create a Bookmark
Save a new URL with a title and description.
from app.services.bookmark_service import BookmarkService
service = BookmarkService()
bookmark, error = service.create_bookmark({
"url": "https://example.com",
"title": "Example Domain",
"description": "A site for examples"
})
Organize with Tags
Create a tag and attach it to a bookmark.
# Create a tag
tag, _ = service.create_tag({"name": "Research", "color": "blue"})
# Update bookmark with the tag ID
service.update_bookmark(bookmark.id, {"tags": [tag.id]})
Search Bookmarks
Perform a full-text search across titles and descriptions.
results = service.search("example", limit=10)
for b in results:
print(f"Found: {b.title}")
Smart Collections
Create a collection that automatically groups bookmarks containing the word "Python".
collection, _ = service.create_collection({
"name": "Python Resources",
"type": "smart",
"filter_rule": "python"
})
When to Use
- Prototyping: Perfect for building bookmarking frontends or browser extensions quickly.
- Internal Tools: Use it as a backend for team-wide link sharing or resource libraries.
- Learning: A great example of a layered Flask architecture with in-memory search and caching.
When Not to Use
- Persistent Storage: The current implementation uses an in-memory repository. Data is lost when the server restarts.
- High Concurrency: While the service is a singleton, it does not currently implement row-level locking or database transactions.
- Large Datasets: The in-memory search and storage are optimized for thousands, not millions, of records.
Integration & Stack
- Framework: Flask 3.0+
- Language: Python 3.10+ (uses modern type hinting and dataclasses)
- Dependencies: Minimal (Flask, python-dotenv)
- Storage: In-memory (pluggable via
BookmarkRepository)
Getting Started Pointers
- Explore the API Reference in the README for a full list of REST routes.
- Check
app/models/bookmark.pyto see the full schema for bookmark entities. - See
app/services/bookmark_service.pyfor the primary business logic entry point.
Limitations
- No Authentication: The API is currently open and does not include user accounts or API keys.
- In-Memory Only: There is no built-in SQLite or Postgres adapter yet.
- Single User: The repository and search index are shared across the entire application instance.
FAQ
How do I persist data?
Currently, you would need to implement a new Repository class that inherits the interface of BookmarkRepository and connects to a database like SQLite or PostgreSQL.
Is the search case-sensitive?
No, the SearchIndex tokenizes and lowercases both the content and the query for case-insensitive matching.
What happens when I delete a tag?
The BookmarkService.delete_tag method automatically removes that tag ID from all bookmarks that were using it before deleting the tag itself.
Can a bookmark be in multiple collections? Yes. Since collections just store lists of bookmark IDs, a single bookmark can appear in any number of manual or smart collections.