Skip to main content

Overview

Pagemark API is a lightweight bookmark management service built with Flask. It provides a structured REST API for saving URLs, organizing them with tags and collections, and performing full-text searches across your saved content.

The Digital Filing Cabinet

Managing a growing list of bookmarks often leads to "link rot" and difficulty finding specific resources. Pagemark solves this by providing a centralized, searchable, and categorizable API that can be integrated into custom dashboards, browser extensions, or personal knowledge management tools.

Core Concepts

  • Bookmark: The primary entity representing a saved URL. It includes a title, description, and metadata, along with a status (active, archived, or trashed).
  • Tag: Flexible, color-coded labels that can be attached to any number of bookmarks for cross-cutting organization.
  • Collection: Named groups used to cluster related bookmarks into a single logical container.
  • Service Layer: The BookmarkService acts as a single point of entry for business logic, ensuring that operations like "delete tag" correctly clean up references across all bookmarks.
  • In-Memory Storage: By default, the system uses an in-memory repository and search index, making it extremely fast but ephemeral.

How It Works

The application follows a strict layered architecture to separate concerns:

  1. Routes: Flask blueprints (e.g., app.routes.bookmarks) handle HTTP serialization and status codes.
  2. Orchestration: The BookmarkService validates incoming data and coordinates between the repository, search index, and cache.
  3. Search Index: An inverted index (SearchIndex) provides full-text search capabilities by tokenizing titles and descriptions.
  4. Caching: An LRUCache stores frequently accessed bookmarks to minimize repository lookups.
  5. Repository: The BookmarkRepository abstracts data access, allowing the underlying storage to be swapped without changing business logic.

Use Cases

Save and Tag a Bookmark

Create a new entry with metadata and associate it with existing tags.

from app.services.bookmark_service import BookmarkService

service = BookmarkService()
bookmark, error = service.create_bookmark({
"url": "https://flask.palletsprojects.com/",
"title": "Flask Documentation",
"description": "The official documentation for the Flask web framework.",
"tags": ["dev-tools", "python"]
})

if not error:
print(f"Saved: {bookmark.id}")

Find bookmarks based on keywords in their title or description.

from app.services.bookmark_service import BookmarkService

service = BookmarkService()
# Returns a list of Bookmark objects matching 'python'
results = service.search(query="python", limit=5)

for b in results:
print(f"{b.title}: {b.url}")

Organize into Collections

Group related bookmarks for project-based organization.

from app.services.bookmark_service import BookmarkService

service = BookmarkService()
# Create a collection and add a bookmark to it
collection, _ = service.create_collection({"name": "Reading List"})
success = service.add_to_collection(collection.id, "bookmark_id_123")

Expectations & Compatibility

When to use

  • Prototyping: Ideal for building front-end bookmarking tools without setting up a heavy database.
  • Personal Tools: Perfect for small-scale, local-first applications where speed is a priority.
  • Educational Reference: A clean example of how to implement the Service and Repository patterns in Flask.

When not to use

  • Persistent Storage: Since the default repository is in-memory, all data is lost when the server restarts.
  • Large Datasets: The search index and repository are not optimized for millions of records.
  • High Concurrency: The in-memory implementation does not include row-level locking or transaction support.

Stack Compatibility

  • Language: Python 3.8+
  • Framework: Flask 3.0+
  • Dependencies: python-dotenv for configuration management.
  • Deployment: Can be run as a standard WSGI application (e.g., with Gunicorn).

Getting Started Pointers

  • Explore the API Reference to see the full REST specification.
  • Check app/config.py to switch between DevelopmentConfig and ProductionConfig.
  • Review app/models/bookmark.py to understand the data structure of a bookmark.

Limitations & Assumptions

  • No Authentication: The current version does not include user authentication or multi-tenancy; all bookmarks are shared.
  • Soft Deletion: Deleting a bookmark via the API moves it to the trashed status rather than removing it from memory immediately.
  • Manual Indexing: The search index is updated incrementally on writes but must be rebuilt if the underlying repository is modified externally.

FAQ

Does it support nested collections? No, collections are currently flat. You can group bookmarks into a collection, but you cannot put a collection inside another collection.

How do I persist my data? You would need to implement a new version of BookmarkRepository (e.g., using SQLAlchemy or Motor) and inject it into the BookmarkService.

Is the search case-sensitive? No, the SearchIndex tokenizes and searches in lowercase to ensure case-insensitive matching.

Can I add custom metadata to bookmarks? Yes, the Bookmark model includes a metadata dictionary field that accepts arbitrary key-value pairs.

What happens if I delete a tag that is in use? The BookmarkService.delete_tag method automatically removes that tag ID from all bookmarks before deleting the tag itself.