# How to Integrate Douban Data with LunaTV: A Complete Guide Using the Core Fetch Client

> Learn to integrate Douban data with LunaTV using the Core Fetch Client. This guide shows you how to import and call fetchDoubanData to get typed movie and TV show info.

- Repository: [MoonTechLab/LunaTV](https://github.com/MoonTechLab/LunaTV)
- Tags: how-to-guide
- Published: 2026-09-09

---

**Integrating Douban data with LunaTV requires importing the `fetchDoubanData` helper from [`src/lib/douban.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/douban.ts) and calling it with a valid Douban API URL to retrieve typed movie and TV show information.**

The **MoonTechLab/LunaTV** repository provides a lightweight, reusable client for consuming Douban's public endpoints. This client wraps the native `fetch` API with proper headers, timeout handling, and TypeScript generics, making it straightforward to pull movie metadata, recommendations, and category listings into your application.

## Understanding the Douban Client Architecture

### Core Fetch Helper in [`src/lib/douban.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/douban.ts)

The heart of the integration lives in [`src/lib/douban.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/douban.ts), which exports a generic `fetchDoubanData<T>()` function. This utility configures a 10-second timeout, sets browser-like headers to satisfy Douban's CORS policies, and returns a strongly-typed JSON response.

```typescript
// src/lib/douban.ts
export async function fetchDoubanData<T>(url: string): Promise<T> {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 10_000);
  
  const fetchOptions = {
    signal: controller.signal,
    headers: {
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
      'Referer': 'https://movie.douban.com/',
      'Accept': 'application/json, text/plain, */*',
      'Origin': 'https://movie.douban.com',
    },
  };

  try {
    const response = await fetch(url, fetchOptions);
    clearTimeout(timeoutId);
    
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    clearTimeout(timeoutId);
    throw error;
  }
}

```

The function accepts a generic type parameter `T`, allowing callers to specify the expected response shape at compile time. It handles network timeouts via `AbortController` and clears the timer in both success and error paths to prevent memory leaks.

## Built-in API Routes for Douban Integration

LunaTV exposes server-side endpoints under `src/app/api/douban/` that act as thin wrappers around the core fetch helper. These routes centralize Douban-specific logic and provide clean URLs for frontend consumption.

### Generic Proxy Endpoint ([`src/app/api/douban/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/douban/route.ts))

The base route forwards requests to Douban while reusing the shared client configuration. It accepts a target URL or ID parameter, delegates the actual HTTP call to `fetchDoubanData`, and streams the JSON response back to the caller.

### Recommendations Endpoint ([`src/app/api/douban/recommends/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/douban/recommends/route.ts))

This route implements Douban's recommendation feed (e.g., `https://movie.douban.com/j/radio/people`). It constructs the query string, invokes `fetchDoubanData` with an array type, and returns a curated list of recommended movies based on user preferences.

### Categories Endpoint ([`src/app/api/douban/categories/route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/api/douban/categories/route.ts))

Located at the same directory level, this endpoint fetches Douban's genre taxonomy—such as "剧情" (Drama) or "喜剧" (Comedy)—by calling the appropriate Douban JSON endpoint through the shared helper and returning the parsed category list.

## Step-by-Step Integration Guide

### Step 1: Import the Fetch Helper

Begin by importing the client into your server-side route or utility file. The helper is framework-agnostic and works in Next.js API routes, React Server Components, or Node.js scripts.

```typescript
import { fetchDoubanData } from '@/lib/douban';

```

### Step 2: Define TypeScript Interfaces

Create interfaces that match Douban's JSON structure to enable type safety and IntelliSense. Place these in [`src/lib/types.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/types.ts) or a dedicated [`douban.types.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/douban.types.ts) file.

```typescript
// src/lib/douban.types.ts
export interface DoubanMovie {
  id: string;
  title: string;
  rating: {
    value: string;
    count: number;
  };
  year: string;
  genres: string[];
  abstracts?: string;
}

export interface DoubanRecommendation {
  id: string;
  title: string;
  image: string;
  recommendation_reason: string;
}

```

### Step 3: Call the Douban API

Use the helper to fetch specific resources by constructing the appropriate Douban URL. Always pass your defined interface as the generic parameter.

```typescript
// Example: Fetching a specific movie's details
import { fetchDoubanData } from '@/lib/douban';
import type { DoubanMovie } from '@/lib/douban.types';

export async function getMovieById(id: string): Promise<DoubanMovie> {
  const url = `https://movie.douban.com/j/subject_abstract?subject_id=${id}`;
  
  return await fetchDoubanData<DoubanMovie>(url);
}

```

For recommendations, point to the built-in endpoint or call Douban directly:

```typescript
import { fetchDoubanData } from '@/lib/douban';
import type { DoubanRecommendation } from '@/lib/douban.types';

export async function getRecommendations(userId: string): Promise<DoubanRecommendation[]> {
  const url = `https://movie.douban.com/j/radio/people?user_id=${userId}&type=rec`;
  
  return await fetchDoubanData<DoubanRecommendation[]>(url);
}

```

### Step 4: Consume from the Frontend

Call your internal API routes from React components using standard fetch or data-fetching libraries like SWR.

```tsx
// components/RecommendationList.tsx
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

export default function RecommendationList({ userId }: { userId: string }) {
  const { data, error } = useSWR(
    `/api/douban/recommends?uid=${userId}`,
    fetcher
  );

  if (error) return <div>Failed to load recommendations</div>;
  if (!data) return <div>Loading...</div>;

  return (
    <ul>
      {data.map((movie: any) => (
        <li key={movie.id}>{movie.title}</li>
      ))}
    </ul>
  );
}

```

## Extending the Douban Integration

### Adding Custom Endpoints

To integrate additional Douban features (such as search or reviews), create a new folder under `src/app/api/douban/` with a [`route.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/route.ts) file. Import `fetchDoubanData`, construct the target URL, and return the processed result.

```typescript
// src/app/api/douban/search/route.ts
import { fetchDoubanData } from '@/lib/douban';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const query = searchParams.get('q');
  
  if (!query) {
    return NextResponse.json({ error: 'Missing query' }, { status: 400 });
  }

  const doubanUrl = `https://movie.douban.com/j/subject_suggest?q=${encodeURIComponent(query)}`;
  const results = await fetchDoubanData<any[]>(doubanUrl);
  
  return NextResponse.json(results);
}

```

### Implementing Response Caching

For high-traffic scenarios, wrap `fetchDoubanData` with the existing cache utility in [`src/lib/search-cache.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/search-cache.ts). This memoizes frequent calls (such as popular movie details) to reduce latency and respect Douban's rate limits.

```typescript
import { fetchDoubanData } from '@/lib/douban';
import { cache } from '@/lib/search-cache';

export const getCachedMovie = cache(
  async (id: string) => {
    return await fetchDoubanData(`https://movie.douban.com/j/subject_abstract?subject_id=${id}`);
  },
  { ttl: 3600 } // Cache for 1 hour
);

```

## Summary

- **The `fetchDoubanData` function** in [`src/lib/douban.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/douban.ts) provides the foundational HTTP client with timeout handling and browser headers required to communicate with Douban's endpoints.
- **Built-in API routes** under `src/app/api/douban/` offer ready-to-use endpoints for recommendations and categories, all consuming the core helper.
- **Type-safe integration** is achieved by passing generic type parameters when calling the fetch helper, ensuring compile-time validation of Douban's JSON responses.
- **Extension points** exist for adding custom search endpoints or implementing caching layers using existing utilities like [`src/lib/search-cache.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/search-cache.ts).

## Frequently Asked Questions

### How do I handle Douban API rate limits in LunaTV?

The `fetchDoubanData` client does not implement built-in rate limiting, but you can mitigate throttling by wrapping calls with the `cache` utility from [`src/lib/search-cache.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/search-cache.ts) to memoize responses. For production deployments, implement a request queue or proxy middleware that adds exponential backoff between retries.

### Can I use the Douban client on the client side (browser)?

While `fetchDoubanData` relies on standard `fetch` and can technically run in browsers, Douban's CORS policies typically block direct cross-origin requests from frontend code. As implemented in LunaTV, you should route requests through the server-side API endpoints under `src/app/api/douban/` to avoid CORS errors.

### What data format does `fetchDoubanData` return?

The function returns a Promise resolving to the parsed JSON object. By supplying a generic type parameter (e.g., `fetchDoubanData<DoubanMovie>(url)`), you instruct TypeScript to treat the response as that specific interface, though the actual runtime value depends on Douban's live API response structure.

### Where should I define TypeScript interfaces for Douban responses?

Define interfaces in a dedicated file such as [`src/lib/douban.types.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/douban.types.ts) or extend the existing [`src/lib/types.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/types.ts). Import these types wherever you invoke `fetchDoubanData` to maintain type safety across your integration points.