# Frontend Integration with REST API using TanStack Query and Zustand in Open-Notebook

> Integrate Open-Notebook's REST API with TanStack Query and Zustand for seamless frontend state management. Learn how Next.js handles server and client state efficiently.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-07-06

---

**Open-Notebook's Next.js 16 frontend communicates with its FastAPI backend through a REST API layer managed by TanStack Query for server state and Zustand for client-side UI state.**

The open-notebook repository (lfnovo/open-notebook) implements a TypeScript React architecture that strictly separates remote data synchronization from local interface preferences. This guide examines how the frontend leverages TanStack Query for declarative REST API interactions while using Zustand for lightweight, persistent UI state management.

## Architecture Overview

The integration follows a clean separation of concerns: **TanStack Query** (`@tanstack/react-query`) handles all server-derived state—fetching, caching, and mutating data from the FastAPI backend—while **Zustand** manages client-only state like themes and view modes. This prevents accidental cache corruption and ensures UI components remain responsive during long-running LLM operations.

The data flow follows this path: REST endpoints → generic Axios client → domain API modules → TanStack Query hooks → React components. Zustand stores exist alongside this pipeline, providing immediate access to user preferences without network calls.

## The API Client Layer

### Axios Configuration with Dynamic Base URL

At the foundation lies [`frontend/src/lib/api/client.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/client.ts), a thin Axios wrapper that configures runtime parameters. It injects the base URL dynamically and attaches authentication tokens from the persisted `auth-storage` store. Critically, it sets a **10-minute timeout** to accommodate long-running LLM calls common in notebook operations.

```typescript
// Conceptual representation of the client configuration
import axios from 'axios';

export const apiClient = axios.create({
  timeout: 600000, // 10 minutes for LLM operations
  headers: {
    Authorization: `Bearer ${getAuthToken()}`
  }
});

```

## Domain-Specific API Modules

Each backend resource maps to a dedicated service module. For example, [`frontend/src/lib/api/notebooks.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/notebooks.ts) forwards calls to the generic client, providing typed methods for notebook operations. These modules remain thin, containing only HTTP logic and response typing, leaving state management to the hook layer.

## Managing Server State with TanStack Query

### Query Hooks and Cache Keys

The [`frontend/src/lib/hooks/use-notebooks.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/hooks/use-notebooks.ts) file defines the data fetching strategy. It exports `useNotebooks`, which wraps `useQuery` with a structured key system (`QUERY_KEYS.notebooks`) and parameterized fetching based on the `archived` flag.

```typescript
// From frontend/src/lib/hooks/use-notebooks.ts
import { useQuery } '@tanstack/react-query';

export function useNotebooks(archived: boolean = false) {
  return useQuery({
    queryKey: [QUERY_KEYS.notebooks, { archived }],
    queryFn: () => fetchNotebooks({ archived }),
  });
}

```

### Mutations and Automatic Cache Invalidation

Create, update, and delete operations use `useMutation` hooks that automatically invalidate stale cache entries. When `useCreateNotebook` succeeds, it triggers invalidation of both the list key (`QUERY_KEYS.notebooks`) and specific entity keys (`QUERY_KEYS.notebook(id)`), ensuring UI components display fresh data without manual refresh.

```typescript
// From frontend/src/lib/hooks/use-notebooks.ts (lines 24-38)
export function useCreateNotebook() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: createNotebookApi,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.notebooks] });
    },
    onError: (error) => {
      showToast(getApiErrorKey(error));
    }
  });
}

```

## Managing Client State with Zustand

### Persistent Theme Management

UI-wide preferences live in [`frontend/src/lib/stores/theme-store.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/stores/theme-store.ts). The store uses Zustand's `persist` middleware to save the user's theme selection (light/dark/system) to `localStorage`, surviving page reloads. The `setTheme` method also updates the `<html>` element's class list to apply Tailwind dark mode classes immediately.

```typescript
// From frontend/src/lib/stores/theme-store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useThemeStore = create(
  persist(
    (set) => ({
      theme: 'system',
      setTheme: (theme) => {
        set({ theme });
        document.documentElement.classList.toggle('dark', theme === 'dark');
      },
      isDark: () => theme === 'dark' || (theme === 'system' && systemPrefersDark())
    }),
    { name: 'theme-storage' }
  )
);

```

### Notebook View Preferences

Similarly, [`frontend/src/lib/stores/notebook-view-store.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/stores/notebook-view-store.ts) manages the notebook display mode (tile vs. list layout). This state changes frequently during user interaction but never requires API synchronization, making it ideal for Zustand's client-side storage.

## Error Handling and UI Feedback

The [`frontend/src/lib/hooks/use-toast.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/hooks/use-toast.ts) utility provides consistent notification UI across all mutations. Each mutation's `onError` handler extracts human-readable error keys using `getApiErrorKey` and dispatches them through the toast system, ensuring uniform error presentation without component-level boilerplate.

## Code Examples in Practice

Fetch notebooks with loading states and automatic background updates:

```typescript
import { useNotebooks } '@/lib/hooks/use-notebooks';
import { LoadingSpinner } '@/components/common/LoadingSpinner';

export default function NotebookList({ showArchived }: { showArchived?: boolean }) {
  const { data: notebooks = [], isLoading, isError } = useNotebooks(showArchived ?? false);

  if (isLoading) return <LoadingSpinner />;
  if (isError) return <p>Failed to load notebooks.</p>;

  return (
    <ul>
      {notebooks.map(nb => (
        <li key={nb.id}>{nb.title}</li>
      ))}
    </ul>
  );
}

```

Create a notebook and automatically refresh the list:

```typescript
import { useCreateNotebook } '@/lib/hooks/use-notebooks';
import { useState } from 'react';

export function NewNotebookForm() {
  const [title, setTitle] = useState('');
  const createNotebook = useCreateNotebook();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await createNotebook.mutateAsync({ title });
    setTitle('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={title} 
        onChange={e => setTitle(e.target.value)} 
        placeholder="Notebook title" 
      />
      <button type="submit" disabled={createNotebook.isLoading}>
        Create
      </button>
    </form>
  );
}

```

Toggle themes with persistent storage:

```typescript
import { useTheme } '@/lib/stores/theme-store';

export function ThemeToggle() {
  const { theme, setTheme } = useTheme();

  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      Switch to {theme === 'dark' ? 'Light' : 'Dark'} Mode
    </button>
  );
}

```

## Summary

- **TanStack Query** in [`frontend/src/lib/hooks/use-notebooks.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/hooks/use-notebooks.ts) manages all server state, providing automatic caching, background refetching, and optimistic updates for REST API resources.
- **Zustand** stores in [`frontend/src/lib/stores/theme-store.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/stores/theme-store.ts) and [`notebook-view-store.ts`](https://github.com/lfnovo/open-notebook/blob/main/notebook-view-store.ts) handle client-only UI state with `persist` middleware for `localStorage` durability.
- The **API client** at [`frontend/src/lib/api/client.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/client.ts) configures a 10-minute timeout and dynamic auth injection to support long-running LLM operations.
- **Cache invalidation** happens automatically after mutations, ensuring UI consistency without manual state management.
- **Error handling** flows through `getApiErrorKey` and `useToast`, standardizing failure feedback across the application.

## Frequently Asked Questions

### How does Open-Notebook handle authentication in API requests?

The [`frontend/src/lib/api/client.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/client.ts) file retrieves tokens from the persisted `auth-storage` store and injects them into Axios request headers. This happens transparently for all domain modules like [`frontend/src/lib/api/notebooks.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/notebooks.ts), ensuring authenticated calls without repeating auth logic in every hook.

### What is the difference between TanStack Query and Zustand in this architecture?

**TanStack Query** manages server-derived state—data that originates from the FastAPI backend and requires network synchronization. It handles caching, refetching, and mutation side effects. **Zustand** manages client-only state—UI preferences like themes and view modes that never need to sync with the backend but must persist across sessions using `localStorage`.

### How does cache invalidation work when creating or updating notebooks?

After a successful mutation in `useCreateNotebook` or `useUpdateNotebook`, the hook calls `queryClient.invalidateQueries()` with the specific `QUERY_KEYS` (e.g., `QUERY_KEYS.notebooks` or `QUERY_KEYS.notebook(id)`). This marks cached data as stale, triggering automatic refetching in any mounted component that subscribes to those keys.

### Why does the API client use a 10-minute timeout?

The timeout configured in [`frontend/src/lib/api/client.ts`](https://github.com/lfnovo/open-notebook/blob/main/frontend/src/lib/api/client.ts) accommodates long-running LLM operations common in Open-Notebook's workflow, such as generating summaries or processing sources. Standard HTTP timeouts would interrupt these legitimate backend processes, so the extended duration ensures completion without false error states.