# Open-Notebook Three-Tier Architecture: Frontend, API, and Database Communication Explained

> Explore Open-Notebooks three-tier architecture: React frontend, FastAPI API, and SurrealDB database. Understand inter-layer communication via HTTP REST SurrealQL and Esperanto AI.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: architecture
- Published: 2026-06-14

---

**TLDR:** Open-notebook implements a strict three-tier architecture using React/Next.js for the frontend, FastAPI for the API layer, and SurrealDB for the database, with communication flowing through HTTP/REST, SurrealQL queries, and the Esperanto AI abstraction library.

Open-notebook is organized as a production-ready, open-source notebook application with clear separation of concerns across three distinct layers. Understanding the open-notebook three-tier architecture is essential for contributors customizing the codebase or deploying scalable instances. The architectural diagram in the repository's root [`CLAUDE.md`](https://github.com/lfnovo/open-notebook/blob/main/CLAUDE.md) visualizes this stack, showing how each tier handles specific responsibilities while communicating through well-defined interfaces.

## Three-Tier Architecture Overview

The system divides responsibilities across the classic presentation, application, and data tiers, enabling independent scaling and technology swaps.

### Frontend Layer (React/Next.js/TypeScript)

The presentation layer is built with **React** and **Next.js**, handling UI components for notebooks, sources, notes, chat sessions, podcasts, and search. State management relies on **Zustand**, while **TanStack Query** manages server state and data fetching. UI components come from **shadcn/ui**, providing a consistent design system across the application.

### API Layer (FastAPI/Python 3.11+)

The middle tier exposes **REST endpoints** for all domain objects and orchestrates business logic. It runs **LangGraph** workflows for AI operations and queues asynchronous jobs for long-running tasks. The API selects AI providers dynamically through the **Esperanto** library, abstracting differences between OpenAI, Anthropic, and other models.

### Database Layer (SurrealDB)

The data tier uses **SurrealDB**, a graph database that stores notebooks, sources, notes, chat sessions, credentials, and vector embeddings for semantic search. Unlike traditional relational databases, SurrealDB handles graph relationships natively, allowing efficient traversal of connections between notebooks and their sources.

## Inter-Layer Communication Flows

Each layer communicates only with its adjacent tier, maintaining loose coupling and clear contracts.

### Frontend to API via HTTP/REST

The frontend communicates with the API through a thin wrapper located in [`frontend/src/lib/api/client.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/client.ts), which configures an Axios instance pointing at the API base URL (default `http://localhost:5055`). Domain-specific modules like [`frontend/src/lib/api/notebooks.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/notebooks.ts) encapsulate endpoint logic, performing typed HTTP requests that the FastAPI layer handles.

The API returns **Pydantic**-validated JSON responses defined in [`api/models.py`](https://github.com/lfnovo/open-notebook/blob/main/api/models.py), which the frontend parses into TypeScript interfaces defined in [`frontend/src/lib/types/api.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/types/api.ts).

### API to Database via SurrealQL

The API layer interacts with SurrealDB through a repository abstraction in [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py). This helper manages connection pooling, authentication, and conversion of `RecordID` objects to strings. Routers build **SurrealQL** queries—such as `SELECT *, count(<-reference.in) AS source_count FROM notebook`—and execute them via the `repo_query` function, ensuring a clean async interface that handles graph traversals and aggregations.

### API to AI Providers via Esperanto

LangGraph workflows (e.g., [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py)) request models through the `ModelManager`, which routes to appropriate providers using the unified **Esperanto** interface. This abstraction allows the system to switch between OpenAI, Anthropic, and other providers without changing workflow code.

### Async Job Processing via Surreal-Commands

For long-running operations like podcast generation, the API dispatches jobs via the Surreal-Commands system implemented in [`api/podcast_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/podcast_service.py). The API returns a command ID immediately, and clients poll `/commands/{command_id}` to track execution progress, preventing HTTP timeouts while maintaining request traceability.

## Code Examples: End-to-End Request Flow

The following examples trace a notebook listing request through all three tiers.

**Frontend API Wrapper**

```ts
// frontend/src/lib/api/notebooks.ts
import apiClient from './client'

export const notebooksApi = {
  list: async (params?: { archived?: boolean; order_by?: string }) => {
    const response = await apiClient.get<NotebookResponse[]>('/notebooks', { params })
    return response.data
  },
}

```

**FastAPI Endpoint**

```python

# api/routers/notebooks.py

@router.get("/notebooks", response_model=List[NotebookResponse])
async def get_notebooks(
    archived: Optional[bool] = Query(None),
    order_by: str = Query("updated desc"),
):
    # Validate order_by …

    query = f"""
        SELECT *,
        count(<-reference.in) as source_count,
        count(<-artifact.in) as note_count
        FROM notebook
        ORDER BY {validated_order_by}
    """
    result = await repo_query(query)
    # Optional filtering by archived …

    return [
        NotebookResponse(
            id=str(nb.get("id", "")),
            name=nb.get("name", ""),
            # …

            source_count=nb.get("source_count", 0),
            note_count=nb.get("note_count", 0),
        )
        for nb in result
    ]

```

**Database Repository Execution**

```python

# open_notebook/database/repository.py

async def repo_query(query_str: str, vars: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
    async with db_connection() as connection:
        result = parse_record_ids(await connection.query(query_str, vars))
        if isinstance(result, str):
            raise RuntimeError(result)
        return result

```

## Summary

- **Open-notebook** organizes code into three distinct tiers: React/Next.js frontend, FastAPI backend, and SurrealDB database.
- **Communication flows** are strictly defined: HTTP/REST between frontend and API, SurrealQL between API and database, and Esperanto for AI provider abstraction.
- **Repository pattern** in [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) abstracts database complexity, handling connection management and RecordID conversion.
- **Async job processing** uses Surreal-Commands to prevent blocking API requests during long-running tasks like podcast generation.
- **Type safety** flows through the stack via Pydantic models (Python) and TypeScript interfaces (frontend).

## Frequently Asked Questions

### What technologies comprise each tier of open-notebook?

The **frontend** uses React/Next.js with TypeScript, Zustand for state, and TanStack Query for data fetching. The **API layer** runs FastAPI on Python 3.11+, handling REST endpoints, LangGraph workflows, and AI provider selection through the Esperanto library. The **database tier** uses SurrealDB, a graph database storing structured data and vector embeddings.

### How does the frontend communicate with the API?

The frontend calls the API through an Axios client configured in [`frontend/src/lib/api/client.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/client.ts), targeting `http://localhost:5055` by default. Domain-specific modules like [`frontend/src/lib/api/notebooks.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/notebooks.ts) encapsulate REST operations, sending HTTP requests that FastAPI routers process and returning typed JSON responses validated by Pydantic schemas.

### How does the API layer query the SurrealDB database?

The API uses a repository abstraction in [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) to execute SurrealQL queries. Routers construct graph-aware queries (e.g., counting inbound relationships with `count(<-reference.in)`) and pass them to `repo_query`, which handles async connections, authentication, and RecordID string conversion, returning plain Python dictionaries.

### How does open-notebook handle switching between AI providers?

The API uses the **Esperanto** library to abstract provider-specific implementations. LangGraph workflows request models through a unified `ModelManager` interface, which routes to OpenAI, Anthropic, or other supported providers without requiring changes to the workflow logic in files like [`open_notebook/graphs/ask.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/ask.py).