Open Notebook Three-Tier Architecture: Next.js Frontend, FastAPI Backend, and SurrealDB Database

Open Notebook implements a classic three-tier architecture with Next.js handling the presentation layer, FastAPI powering the application logic, and SurrealDB managing graph-based data persistence with built-in vector search.

The open-source project lfnovo/open-notebook structures its codebase as a clean separation of concerns across three distinct tiers. This architecture enables independent scaling of the React-based user interface, Python-based AI workflow engine, and graph database backend. Understanding how these layers interact reveals why the system can handle concurrent AI operations while maintaining responsive real-time updates.

Presentation Tier: Next.js Frontend

Located in /frontend/, the presentation tier uses Next.js with React 19 to render notebooks, sources, notes, chat interfaces, podcasts, and search functionality. According to frontend/CLAUDE.md, the frontend relies on Zustand for state management and TanStack Query (React Query) for server state synchronization. UI components come from shadcn/ui styled with Tailwind CSS, creating a cohesive design system without custom CSS overhead.

State Management and Data Fetching

Data fetching happens through TanStack Query hooks that communicate with the FastAPI backend running on port 5055. The apiClient instance configures the base URL via environment variables, typically defaulting to http://localhost:5055. This setup automatically handles caching, background refetching, and optimistic updates without manual Redux boilerplate.

UI Component Architecture

The interface builds upon shadcn/ui primitives, providing accessible and customizable components for complex notebook interfaces. Tailwind CSS handles all styling, ensuring consistent spacing and typography across the application while keeping bundle sizes minimal through tree-shaking.

Application Tier: FastAPI Backend

The middle layer resides in /api/ and /open_notebook/, implementing a FastAPI 0.104+ application that exposes REST endpoints on port 5055. This tier handles all business logic including LangGraph workflows for content ingestion, chat operations, search-and-synthesis pipelines, and content transformations. The architecture uses Esperanto for multi-provider AI orchestration, supporting OpenAI, Anthropic, and Ollama through a unified interface.

Async-First API Design

Every database call, graph operation, and external AI request uses Python's async/await patterns, preventing blocking during I/O operations. In api/main.py, the application configures CORS middleware to mirror allowed origins and implements centralized error handling alongside optional password protection middleware. The async design allows FastAPI to handle thousands of concurrent connections without thread pool exhaustion.

LangGraph Workflow Engine

Complex operations execute as LangGraph workflows defined in modules like open_notebook/graphs/ask.py. These graphs orchestrate multi-step processes such as retrieving relevant sources from SurrealDB, embedding content for semantic search, and synthesizing responses through LLM providers. The workflow engine maintains state across asynchronous operations, enabling long-running AI processes without blocking API requests.

Data Tier: SurrealDB Database

SurrealDB operates on port 8000 as a graph database with native vector search capabilities. It stores core entities including Notebook, Source, Note, ChatSession, and Credential records while maintaining relationships like "source-to-notebook" and "note-to-source" as graph edges. The database automatically handles vector embeddings for semantic search across all content types.

Async Repository Pattern

The open_notebook/database/repository.py module implements db_connection and repo_query helpers that wrap the async SurrealDB driver. These utilities manage connection pooling and query execution, ensuring that FastAPI endpoints can fire multiple concurrent database queries without blocking the event loop. The repository pattern abstracts SurrealQL syntax from the business logic layer.

Automatic Schema Migrations

On API startup, the AsyncMigrationManager class in open_notebook/database/async_migrate.py applies pending schema changes automatically. This ensures that the database structure remains synchronized with the application code across deployments, handling new tables, indexes, and relationship definitions without manual intervention.

Inter-Tier Communication Flow

The three tiers communicate through well-defined interfaces that enforce separation of concerns. The frontend calls REST endpoints exposed by FastAPI, which processes requests through LangGraph workflows before persisting data via async SurrealDB connections.

  1. Frontend to API: TanStack Query sends HTTP requests to http://localhost:5055 with CORS headers configured in api/main.py via the _cors_headers implementation.

  2. API to Database: Endpoints invoke repo_query() from open_notebook/database/repository.py to execute SurrealQL statements asynchronously.

  3. API to AI Providers: The open_notebook/ai/model_manager.py uses Esperanto to route requests to appropriate LLM providers based on configuration and context window requirements.

  4. API to Frontend: JSON responses flow back to TanStack Query, which automatically caches results and updates Zustand stores to re-render React components.

Code Implementation Examples

These practical examples demonstrate the interaction patterns between tiers.

Fetching notebooks from the frontend:

// src/lib/api/notebooks.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient } from './client';

export const useNotebooks = () => {
  return useQuery(['notebooks'], async () => {
    const { data } = await apiClient.get('/api/notebooks');
    return data;
  });
};

Creating a notebook via FastAPI:


# api/routers/notebooks.py

from fastapi import APIRouter, HTTPException
from open_notebook.database.repository import repo_query

router = APIRouter()

@router.post("/api/notebooks")
async def create_notebook(name: str):
    sql = "CREATE notebook CONTENT {name: $name}"
    try:
        result = await repo_query(sql, {"name": name})
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

Executing a LangGraph workflow:


# open_notebook/graphs/ask.py usage pattern

from open_notebook.graphs.ask import ask_graph

async def answer_question(question: str, notebook_id: str):
    state = await ask_graph.ainvoke(
        {"question": question, "notebook_id": notebook_id},
        config={"run_name": "ask"}
    )
    return state["answer"]

Summary

  • Open Notebook separates concerns across three tiers: Next.js for presentation, FastAPI for application logic, and SurrealDB for data persistence.
  • The frontend uses TanStack Query and Zustand for state management, communicating via REST to port 5055.
  • FastAPI implements async-first design patterns with LangGraph workflows and Esperanto-based AI orchestration.
  • SurrealDB handles graph relationships and vector search through async repository patterns with automatic schema migrations.
  • All inter-tier communication uses async I/O to prevent blocking and enable high concurrency.

Frequently Asked Questions

How does the frontend communicate with the FastAPI backend?

The Next.js frontend uses TanStack Query to send HTTP requests to FastAPI endpoints running on port 5055. The apiClient instance configures the base URL through environment variables, while CORS middleware in api/main.py handles cross-origin requests by mirroring allowed origins in the response headers.

Why does Open Notebook use SurrealDB instead of PostgreSQL or MongoDB?

SurrealDB provides native graph database capabilities and built-in vector search in a single system, eliminating the need for separate vector databases like Pinecone or Milvus. According to the source code in open_notebook/database/repository.py, this allows storing relationships like "source-to-notebook" alongside semantic embeddings for AI-powered search without complex polyglot persistence architectures.

How does the backend handle multiple AI providers simultaneously?

The application tier uses the Esperanto library through open_notebook/ai/model_manager.py to abstract provider-specific implementations. This enables routing requests to OpenAI, Anthropic, or Ollama based on configuration, context window requirements, and credential availability without changing the workflow logic in LangGraph graphs.

What prevents database queries from blocking API requests?

All SurrealDB interactions use async/await patterns implemented in open_notebook/database/repository.py. The repo_query function and db_connection helper leverage the async SurrealDB driver, allowing FastAPI to handle other incoming requests while waiting for database I/O to complete, maximizing throughput under concurrent load.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →