Frontend-Backend API Communication and Data Fetching Patterns in Open Notebook
Open Notebook uses a FastAPI REST backend and TanStack React Query on the frontend to handle all data fetching, caching, and state synchronization between the React/Next.js UI and the Python API.
The lfnovo/open-notebook repository implements a clean separation of concerns between its frontend and backend layers. All communication flows over HTTP REST endpoints, with the frontend leveraging TanStack React Query (formerly React Query) to manage server state, caching, and mutations. This architecture ensures a reactive UI that automatically stays in sync with the FastAPI backend without manual request lifecycle management.
FastAPI REST Backend Architecture
The backend exposes a RESTful API built on FastAPI, organized into domain-specific routers and centralized in a single application entry point.
Router Organization
Each domain object has its own dedicated router module under api/routers/. For example:
api/routers/sources.py– Handles CRUD operations for source recordsapi/routers/notebooks.py– Manages notebook creation, listing, and deletionapi/routers/chat.py– Controls chat session management
These routers handle incoming JSON requests and return Pydantic v2 schema objects defined in api/models.py.
Central Application Configuration
The routers are assembled into the main FastAPI application in api/main.py. This file also registers essential middleware including CORS handling, password authentication, and a global exception handler:
# api/main.py (conceptual structure)
from fastapi import FastAPI
from routers import notebooks, sources, chat
app = FastAPI()
app.include_router(notebooks.router)
app.include_router(sources.router)
app.include_router(chat.router)
Data Serialization
All request and response payloads use JSON with strict validation through Pydantic v2 schemas. The backend enforces type safety at the API boundary, ensuring the frontend receives consistently shaped data.
TanStack React Query Frontend
The frontend manages server state through a centralized QueryClient that provides caching, deduplication, and automatic background refetching.
Query Client Configuration
The QueryClient is instantiated in frontend/src/lib/api/query-client.ts with production-ready defaults:
// frontend/src/lib/api/query-client.ts
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes garbage collection
retry: 2,
refetchOnWindowFocus: false,
},
mutations: {
retry: 1,
},
},
});
Typed Query Keys
The same file exports a QUERY_KEYS constant that standardizes cache keys across the application:
// frontend/src/lib/api/query-client.ts
export const QUERY_KEYS = {
notebooks: ['notebooks'] as const,
notebook: (id: string) => ['notebooks', id] as const,
sources: (notebookId?: string) => ['sources', notebookId] as const,
// ... additional keys
};
Provider Setup
The entire component tree is wrapped by QueryProvider in frontend/src/components/providers/QueryProvider.tsx:
// frontend/src/components/providers/QueryProvider.tsx
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '@/lib/api/query-client';
export function QueryProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
Data Fetching Patterns
Components interact with the backend through standardized hooks that abstract the HTTP layer.
Read Operations
List and detail views use useQuery (or useInfiniteQuery for paginated data) combined with the typed query keys:
// frontend/src/components/notebooks/NotebookList.tsx
const { data, isLoading } = useQuery({
queryKey: QUERY_KEYS.notebooks,
queryFn: () =>
fetch(`${process.env.NEXT_PUBLIC_API_URL}/notebooks`)
.then(res => res.json())
});
Write Operations and Cache Invalidation
Mutations follow a consistent pattern in useMutation hooks. After a successful mutation, the query cache is invalidated to trigger automatic refetching of dependent components:
// frontend/src/components/sources/AddSourceButton.tsx
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QUERY_KEYS } from '@/lib/api/query-client';
export function AddSourceButton({ notebookId }: { notebookId: string }) {
const queryClient = useQueryClient();
const addSource = useMutation({
mutationFn: (payload) =>
fetch(`${process.env.NEXT_PUBLIC_API_URL}/sources`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
}).then(r => r.json()),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: QUERY_KEYS.sources(notebookId)
});
},
});
// Component calls addSource.mutate(payload) on user submission
}
Optimistic UI Updates
Some interactive components (such as chat interfaces) implement optimistic updates, immediately modifying the local cache before the server confirms the mutation, then rolling back if the request fails.
Environment Configuration
The frontend builds API URLs from a single environment variable, making environment switching seamless:
- Development: Set
NEXT_PUBLIC_API_URL=http://localhost:5055in.env.local - Production: Point to your deployed API endpoint without modifying component code
This centralization ensures all fetch calls in fetch(\${process.env.NEXT_PUBLIC_API_URL}/endpoint`)` automatically target the correct backend.
Example Flow: Adding a Source
The following sequence illustrates the complete data fetching pattern when a user adds a new source:
- User submits form →
AddSourceButtoncallsaddSource.mutate(payload) - POST request → Frontend sends JSON to
POST /sources(handled byapi/routers/sources.py) - Database operation → FastAPI creates the record and returns the new object
- Cache invalidation →
onSuccesscallback invalidatesQUERY_KEYS.sources(notebookId) - Automatic refetch → React Query refetches the sources list, updating all subscribed components without manual reload
Summary
- FastAPI REST backend in
api/main.pyexposes domain-specific routers underapi/routers/with JSON/Pydantic v2 serialization - TanStack React Query manages all server state through a centralized
QueryClientconfigured infrontend/src/lib/api/query-client.ts - Typed query keys in
QUERY_KEYSensure cache consistency across the application - Mutation pattern combines
useMutationwithqueryClient.invalidateQueries()to keep UI synchronized after writes - Environment-based URL configuration allows seamless switching between development and production backends
Frequently Asked Questions
How does Open Notebook handle cache invalidation after mutations?
When a mutation succeeds, the onSuccess callback in the useMutation hook calls queryClient.invalidateQueries() with the specific query key (e.g., QUERY_KEYS.sources(notebookId)). This marks the cached data as stale, triggering React Query to automatically refetch the data and update all components subscribed to that key.
What caching strategy does the frontend use?
The QueryClient configuration in frontend/src/lib/api/query-client.ts sets a 5-minute stale time and 10-minute garbage collection time. Data remains fresh for 5 minutes before background refetching, and unused cache entries are garbage collected after 10 minutes. The configuration also disables refetching on window focus to prevent unnecessary network requests.
Why does the repository use TanStack React Query instead of fetch or axios directly?
TanStack React Query provides built-in caching, deduplication, error handling, and automatic background refetching that would require significant boilerplate with raw fetch or axios. The library handles complex state synchronization scenarios—such as updating multiple UI components after a mutation—through its centralized cache and query key system.
How is the API base URL configured across different environments?
The frontend reads NEXT_PUBLIC_API_URL from environment variables (typically set in .env.local for development). All components construct URLs using process.env.NEXT_PUBLIC_API_URL, allowing the same codebase to target http://localhost:5055 in development or a production domain without code changes.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →