# How the Storage Abstraction Layer in LunaTV Works: IStorage Interface and Multi-Backend Architecture

> Explore LunaTVs storage abstraction layer. Discover how the IStorage interface and multi-backend architecture manage data seamlessly across LocalStorage, Redis, Upstash, and Kvrocks.

- Repository: [MoonTechLab/LunaTV](https://github.com/MoonTechLab/LunaTV)
- Tags: deep-dive
- Published: 2026-09-08

---

**LunaTV implements a typed `IStorage` interface that abstracts play records, user accounts, and configuration data across four backends—LocalStorage, Redis, Upstash, and Kvrocks—selected at runtime via the `NEXT_PUBLIC_STORAGE_TYPE` environment variable.**

The storage abstraction layer in LunaTV provides a unified, type-safe approach to persistent data management across diverse deployment environments. By declaring all data operations through a single `IStorage` contract defined in [`src/lib/types.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/types.ts), the application decouples business logic from storage mechanics, allowing seamless switching between browser-based localStorage for personal use and distributed Redis clusters for production workloads.

## Core Architecture of the LunaTV Storage Abstraction Layer

### The IStorage Interface Contract

All persistent data—including play records, favorites, user accounts, search history, admin configuration, and skip-config settings—flows through the `IStorage` interface declared in [`src/lib/types.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/types.ts). This contract defines async methods for every data operation, ensuring type safety across the entire codebase. The interface also optionally exposes `migrateData` and `migratePasswords` hooks for structural upgrades and security hardening.

### Factory Pattern and Environment Selection

The runtime selection logic resides in [`src/lib/db.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/db.ts) within the `getStorage` factory function. This module reads the `NEXT_PUBLIC_STORAGE_TYPE` environment variable (defaulting to `localstorage`) and instantiates the appropriate concrete class. The function returns a singleton instance cached in `storageInstance`, ensuring consistent state across API routes while preventing redundant connection overhead.

## Storage Implementations and Backend Options

### LocalStorage for Client-Side Deployments

When `NEXT_PUBLIC_STORAGE_TYPE` is unset or explicitly set to `localstorage`, the factory returns the `LocalStorage` class. This implementation persists data using the browser's native `localStorage` API, making it ideal for single-user deployments or offline-first scenarios where no server-side infrastructure is required.

### Redis, Upstash, and Kvrocks for Production

For server-side or multi-user deployments, LunaTV supports three Redis-compatible backends:

- **RedisStorage**: Connects to self-hosted Redis instances using the `REDIS_URL` environment variable.
- **UpstashRedisStorage**: Targets cloud-hosted Upstash Redis, authenticated via `UPSTASH_URL` and `UPSTASH_TOKEN`.
- **KvrocksStorage**: Interfaces with Kvrocks servers for high-throughput scenarios requiring persistent Redis semantics.

### The BaseRedisStorage Base Class

All Redis-compatible implementations extend `BaseRedisStorage` defined in [`src/lib/redis-base.db.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/redis-base.db.ts). This abstract class implements the full `IStorage` contract using generic Redis commands—hashes for key-value pairs, sets for collections, and lists for sequential data. Concrete subclasses only supply connection details (client instance and service name), eliminating duplicate logic while enforcing consistent data structures across Redis variants.

## Data Organization and Key Naming Conventions

### Namespaced User Data Layout

The storage layer organizes user-specific data using strict key prefixes to prevent collisions and enable efficient querying. In Redis-backed implementations, keys follow the pattern `u:<user>:<type>`:

- `u:<user>:pr` — Hash containing play records indexed by content ID.
- `u:<user>:fav` — Hash of user favorites with metadata.
- `u:<user>:pwd` — String storing the bcrypt-hashed password.
- `u:<user>:sh` — List of recent search keywords, trimmed to the last N entries.
- `u:<user>:skip` — Hash of skip-configuration objects (intro/outro timestamps).

### O(1) Operations and Bulk Deletions

The Redis data structures enable constant-time read and write operations. Bulk deletions—such as `deleteAllPlayRecords`—execute as single `DEL` commands on the respective hash keys, ensuring efficient cleanup without iterating through individual records.

## Migration Utilities and Data Maintenance

The `IStorage` interface optionally includes migration helpers that facilitate schema evolution. Both `UpstashRedisStorage` and `RedisStorage` provide implementations of `migrateData` that convert legacy flat-key structures into the current hash-based format. The `migratePasswords` method upgrades clear-text or weakly hashed credentials to salted bcrypt hashes, enforcing modern security standards without manual database intervention.

## Implementing the Storage Layer in Practice

### Initializing the Storage Singleton

Application code never instantiates storage classes directly. Instead, they import the `DB` wrapper from [`src/lib/db.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/db.ts), which lazily initializes the singleton through `getStorage()`:

```typescript
// src/lib/db.ts (simplified)
function createStorage(): IStorage {
  const type = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
  switch (type) {
    case 'redis':
      return new RedisStorage();
    case 'upstash':
      return new UpstashRedisStorage();
    case 'kvrocks':
      return new KvrocksStorage();
    default:
      return new LocalStorage();
  }
}

```

### Consuming the Abstraction in API Routes

API endpoints in `src/app/api/*` interact with storage through the `db` instance without knowing the underlying backend. The following login route demonstrates this decoupling:

```typescript
// src/app/api/login/route.ts
import { getDB } from '@/lib/db';

const db = getDB();

export async function POST(req: Request) {
  const { username, password } = await req.json();
  const ok = await db.verifyUser(username, password);
  
  return ok 
    ? NextResponse.json({ success: true }) 
    : NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
}

```

Example of persisting playback state:

```typescript
await db.setPlayRecord('alice', 'movie:123', {
  title: 'Inception',
  source_name: 'netflix',
  cover: '/covers/inception.jpg',
  year: '2010',
  index: 1,
  total_episodes: 1,
  play_time: 120,
  total_time: 8880,
  save_time: Date.now(),
  search_title: 'Inception'
});

```

## Summary

- **LunaTV's storage abstraction layer** centers on the `IStorage` interface in [`src/lib/types.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/types.ts), defining contracts for all data operations.
- **Four storage backends**—LocalStorage, Redis, Upstash, and Kvrocks—are selectable via `NEXT_PUBLIC_STORAGE_TYPE` without modifying business logic.
- **BaseRedisStorage** in [`src/lib/redis-base.db.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/redis-base.db.ts) provides shared Redis implementations, while subclasses handle connection specifics.
- **Namespaced key patterns** like `u:<user>:pr` and `u:<user>:fav` ensure O(1) access patterns and efficient bulk operations.
- **Migration helpers** enable zero-downtime schema upgrades and password hashing improvements across compatible backends.

## Frequently Asked Questions

### What is the default storage backend in LunaTV?

If the `NEXT_PUBLIC_STORAGE_TYPE` environment variable is undefined, the system defaults to `localstorage`, returning a `LocalStorage` instance suitable for browser-only deployments. This ensures the application runs immediately after cloning without requiring external database infrastructure.

### How does LunaTV handle data migration between storage versions?

The `IStorage` interface declares optional `migrateData` and `migratePasswords` methods. Redis-backed implementations traverse legacy flat keys and transform them into the current hash and list structures, while simultaneously upgrading password storage to bcrypt. These utilities run as one-off administrative tasks invoked through API endpoints or CLI scripts.

### Can I switch from LocalStorage to Redis without changing application code?

Yes. Changing `NEXT_PUBLIC_STORAGE_TYPE` from `localstorage` to `redis`, `upstash`, or `kvrocks` in your environment configuration suffices. The `getStorage` factory in [`src/lib/db.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/db.ts) instantiates the appropriate class, while the `DB` wrapper ensures all API routes continue functioning identically regardless of the underlying persistence mechanism.

### Where is the storage type configured in LunaTV?

Configuration occurs entirely through the `NEXT_PUBLIC_STORAGE_TYPE` environment variable. Supported values are `localstorage`, `redis`, `upstash`, and `kvrocks`. For Redis variants, additional variables like `REDIS_URL` or `UPSTASH_TOKEN` must be set to establish connections, but no TypeScript code changes are required to swap backends.