# How to Implement Custom Storage Backends in LINEJS: A Complete Guide

> Explore how to implement custom storage backends in LINEJS. Extend BaseStorage and pass your implementation to BaseClient for flexible data management. Learn more.

- Repository: [Evex  Developers/linejs](https://github.com/evex-dev/linejs)
- Tags: how-to-guide
- Published: 2026-03-01

---

**LINEJS abstracts all persistent data operations behind the `BaseStorage` interface, allowing you to implement custom storage backends by extending the abstract class and passing your implementation to the `BaseClient` constructor via the `storage` option.**

LINEJS is a powerful open-source library for building LINE messenger clients. When you implement custom storage backends in LINEJS, you gain full control over how authentication tokens, chat state, and request counters persist across sessions. This guide walks you through the `BaseStorage` contract, built-in reference implementations, and how to integrate your own storage solution.

## Understanding the BaseStorage Contract

All storage implementations in LINEJS must satisfy the abstract contract defined in [`packages/linejs/base/storage/base.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/storage/base.ts). The `BaseStorage` class defines five required methods that handle every persistent operation:

```typescript
export abstract class BaseStorage {
  abstract set(key: Storage["Key"], value: Storage["Value"]): Promise<void>;
  abstract get(key: Storage["Key"]): Promise<Storage["Value"] | undefined>;
  abstract delete(key: Storage["Key"]): Promise<void>;
  abstract clear(): Promise<void>;
  abstract migrate(storage: BaseStorage): Promise<void>;
}

```

The `Storage` type interface defines the expected key and value shapes:

```typescript
export interface Storage {
  Key: string;
  Value: string | number | boolean | null | Record<string | number, LooseType>;
}

```

Every method returns a `Promise`, ensuring that asynchronous operations like network requests or disk I/O do not block the client.

## Built-in Storage Implementations

LINEJS ships with two reference implementations that demonstrate the contract in action.

### MemoryStorage

**MemoryStorage** provides volatile, in-memory storage using a native `Map`. This implementation is ideal for testing or ephemeral sessions where persistence is not required.

Source: [`packages/linejs/base/storage/memory.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/storage/memory.ts)

### FileStorage

**FileStorage** persists data as JSON on the local filesystem. This is the recommended default for Node.js applications that require state to survive process restarts.

Source: [`packages/linejs/base/storage/file.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/storage/file.ts)

```typescript
import { BaseClient } from "@evex/linejs/client";
import { FileStorage } from "@evex/linejs/storage";

const client = new BaseClient({
  device: "ANDROID",
  storage: new FileStorage("./my-linejs-storage.json"),
});

```

## How to Create a Custom Storage Backend

To implement custom storage backends in LINEJS, you need to create a class that satisfies the `BaseStorage` interface. The contract is deliberately minimal, allowing integration with IndexedDB, WebSQL, localForage, Redis, or any remote key-value service.

### Implementing the Core Methods

Your custom class must implement `set`, `get`, `delete`, and `clear`. Here is a minimal in-memory-like implementation suitable for testing:

```typescript
import type { BaseStorage, Storage } from "@evex/linejs/storage";

export class DummyStorage implements BaseStorage {
  private map = new Map<string, Storage["Value"]>();

  async set(key: string, value: Storage["Value"]): Promise<void> {
    this.map.set(key, value);
  }

  async get(key: string): Promise<Storage["Value"] | undefined> {
    return this.map.get(key);
  }

  async delete(key: string): Promise<void> {
    this.map.delete(key);
  }

  async clear(): Promise<void> {
    this.map.clear();
  }

  async migrate(to: BaseStorage): Promise<void> {
    for (const [k, v] of this.map) {
      await to.set(k, v);
    }
  }
}

```

### Handling Data Migration

The **migrate** method is critical for production usage. When users switch storage types (e.g., from `MemoryStorage` to `FileStorage`), LINEJS invokes `migrate` on the old storage instance, passing the new storage as an argument. Your implementation should iterate over all stored entries and forward them to the target storage.

## Real-World Example: IndexedDB Storage

For browser environments, IndexedDB provides durable, structured storage. The LINEJS repository includes a complete reference implementation in [`example/storage/indexedDB.ts`](https://github.com/evex-dev/linejs/blob/main/example/storage/indexedDB.ts) that demonstrates transaction handling and cursor iteration:

```typescript
export class IndexedDBStorage implements BaseStorage {
  // ... constructor & internal helpers ...

  public async set(key: string, value: any): Promise<void> {
    const db = await this.open();
    const tx = db.transaction(this.storeName, "readwrite");
    await successToPromise(tx.objectStore(this.storeName).put({ key, value }));
    await completeToPromise(tx);
  }

  public async get(key: string): Promise<any | undefined> {
    const db = await this.open();
    const tx = db.transaction(this.storeName);
    const result = await successToPromise(tx.objectStore(this.storeName).get(key));
    await completeToPromise(tx);
    return result?.value;
  }

  public async delete(key: string): Promise<void> {
    const db = await this.open();
    const tx = db.transaction(this.storeName, "readwrite");
    await successToPromise(tx.objectStore(this.storeName).delete(key));
    await completeToPromise(tx);
  }

  public async clear(): Promise<void> {
    const db = await this.open();
    const version = db.version;
    db.close();
    const req = indexedDB.open(this.dbName, version + 1);
    // onupgradeneeded recreates the object store – effectively clearing it
    await successToPromise(req);
  }

  public async migrate(storage: BaseStorage): Promise<void> {
    const db = await this.open();
    const tx = db.transaction(this.storeName, "readwrite");
    const cursor = tx.objectStore(this.storeName).openCursor();
    // iterate cursor, copy each item into `storage`
  }
}

```

This implementation handles IndexedDB’s asynchronous transaction model while satisfying the `BaseStorage` contract.

## Integrating Custom Storage with BaseClient

Once your custom storage class is complete, instantiate it and pass it to the `BaseClient` constructor via the `ClientInit.storage` option. The client constructor is defined in [`packages/linejs/base/core/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/mod.ts):

```typescript
import { BaseClient } from "@evex/linejs/client";
import { IndexedDBStorage } from "./my-storage/indexeddb.ts";

const client = new BaseClient({
  device: "DESKTOPWIN",
  storage: new IndexedDBStorage(),   // ← custom backend
});

```

All internal modules—authentication, request throttling, end-to-end encryption state, and chat history—will now use your custom storage implementation for persistence.

## Summary

- **BaseStorage** is the abstract contract ([`packages/linejs/base/storage/base.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/storage/base.ts)) that defines `set`, `get`, `delete`, `clear`, and `migrate` methods.
- **MemoryStorage** and **FileStorage** are built-in reference implementations for volatile and Node.js filesystem persistence.
- To implement custom storage backends in LINEJS, create a class that satisfies the `BaseStorage` interface and handles the `Storage` key/value types.
- The **migrate** method enables seamless data transfer when switching storage providers.
- Pass your custom instance to `BaseClient` via the `storage` option in `ClientInit`.

## Frequently Asked Questions

### What methods must a custom storage backend implement?

A custom storage backend must implement five abstract methods defined in `BaseStorage`: `set`, `get`, `delete`, `clear`, and `migrate`. Each method must return a `Promise` and handle the `Storage["Key"]` (string) and `Storage["Value"]` (string, number, boolean, null, or Record) types specified in [`packages/linejs/base/storage/base.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/storage/base.ts).

### How do I migrate data from one storage backend to another?

LINEJS handles migration automatically when you switch storage implementations. The client calls `migrate` on the old storage instance, passing the new storage as an argument. Your `migrate` implementation should iterate over all existing key/value pairs and call `await to.set(key, value)` for each entry, effectively copying the dataset to the new backend.

### Can I use browser-based storage like IndexedDB or localStorage?

Yes. The `BaseStorage` contract is environment-agnostic, allowing you to wrap any browser storage API. The repository includes a complete IndexedDB example in [`example/storage/indexedDB.ts`](https://github.com/evex-dev/linejs/blob/main/example/storage/indexedDB.ts) that demonstrates transaction handling, cursor iteration for `migrate`, and proper async/await patterns compatible with the LINEJS client.

### Where do I pass my custom storage instance when initializing the client?

Pass your storage instance via the `storage` property in the `ClientInit` options object when constructing `BaseClient`. The constructor is located in [`packages/linejs/base/core/mod.ts`](https://github.com/evex-dev/linejs/blob/main/packages/linejs/base/core/mod.ts) and accepts an object conforming to the `ClientInit` interface, where `storage` must be an instance of a class implementing `BaseStorage`.