Skip to main content

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), and trashed (soft-deleted).

Architecture

The application follows a clean, layered architecture designed for testability and performance:

  1. Routes: Flask blueprints (e.g., app.routes.bookmarks) handle HTTP serialization and status codes.
  2. Service Layer: The BookmarkService (a singleton) orchestrates business logic, validation, and cross-entity updates.
  3. Search Index: An in-memory SearchIndex provides full-text search capabilities using an inverted index.
  4. Repository: The BookmarkRepository abstracts data access. In the current version, this is an in-memory store.
  5. Caching: An internal LRUCache speeds 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.py to see the full schema for bookmark entities.
  • See app/services/bookmark_service.py for 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.