# What Database Does FreeLLMAPI Use for API Key Storage?

> Discover how FreeLLMAPI stores API keys using a SQLite database. Learn about the configuration and connection factory for secure key management.

- Repository: [Tashfeen/freellmapi](https://github.com/tashfeenahmed/freellmapi)
- Tags: internals
- Published: 2026-08-29

---

**FreeLLMAPI persists API keys in a SQLite database** using the `better-sqlite3` driver with a fallback to Node.js's native `node:sqlite`, configured via the `DATABASE_URL` environment variable and managed through the connection factory in [`server/src/db/index.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/db/index.ts).

FreeLLMAPI is an open-source LLM routing server that uses a lightweight, file-based database architecture for all runtime persistence. The system abstracts database access through a minimal TypeScript interface that supports both persistent disk storage and in-memory instances, making it suitable for both development and production deployments without requiring external database servers.

## SQLite as the Core Storage Engine

FreeLLMAPI uses **SQLite** as its primary database engine for storing API keys and configuration data. According to the `.env.example` template, administrators configure the database location using the `DATABASE_URL` environment variable with the `sqlite:` protocol prefix:

```bash

# Persistent file storage

DATABASE_URL="sqlite:/data/freellmapi.sqlite"

# In-memory database for development

DATABASE_URL="sqlite::memory:"

```

The SQLite approach eliminates the need for separate database server infrastructure. All data, including encrypted API keys in the `api_keys` table, resides in a single file that the application accesses through SQL statements.

## Database Connection Architecture

### Initialization with `initDb` and `getDb`

The database layer implements a singleton pattern to maintain a single connection instance throughout the application lifecycle. The `initDb` function establishes the connection on server startup, while `getDb` provides global access to the cached instance:

```typescript
import { initDb, getDb } from './server/src/db/index.js';

// Initialize once during server bootstrap
initDb('/data/freellmapi.sqlite');

// Access the singleton instance elsewhere
const db = getDb();
const row = db.prepare('SELECT * FROM api_keys WHERE id = ?').get(keyId);

```

As implemented in [`server/src/db/index.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/db/index.ts), calling `getDb()` before `initDb()` throws an explicit error: *"Database not initialized – call `initDb` first."* This ensures the connection pool is properly established before any service queries the database.

### The `connectDb` Factory Function

The `connectDb` function serves as the primary connection factory, accepting either a string path or a `ConnectOptions` object. It automatically creates parent directories when `ensureDir` is true (the default) and returns a database object exposing `prepare()` and `transaction()` methods:

```typescript
export function connectDb(
  conn: string | ConnectOptions, 
  { factory = defaultFactory, ensureDir = true } = {}
): Db {
  const path = typeof conn === 'string' ? conn : conn.path;
  if (ensureDir) {
    const dir = require('path').dirname(path);
    require('fs').mkdirSync(dir, { recursive: true });
  }
  const raw = factory(path) as any;
  return {
    prepare: (sql) => raw.prepare(sql),
    transaction: (fn) => raw.transaction(fn),
  } as Db;
}

```

This abstraction allows the rest of the codebase—such as [`server/src/services/router.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/router.ts)—to execute SQL queries without depending on specific SQLite driver implementations.

## Driver Implementation: better-sqlite3 with Node.js Fallback

FreeLLMAPI prefers the **better-sqlite3** native module for performance but includes graceful degradation logic. The `defaultFactory` function in [`server/src/db/index.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/db/index.ts) attempts to load `better-sqlite3` first, then falls back to the built-in `node:sqlite` module:

```typescript
export const defaultFactory = (path: string) => {
  try {
    const BetterSqlite = runtimeRequire('better-sqlite3') as new (path: string) => unknown;
    return new BetterSqlite(path);
  } catch (e) {
    // Fallback for newer Node versions or Android/Termux environments
    const nodeSqlite = nodeSqliteFactory(path);
    return nodeSqlite;
  }
};

```

This dual-driver strategy ensures compatibility across different deployment targets, including Android and Termux environments where native module compilation may fail, while maintaining high-performance synchronous queries on standard Node.js deployments.

## API Key Storage Schema

API keys are stored in the **`api_keys`** table created via migration files located in `server/src/db/migrations/`. The application queries this table to select active credentials for routing LLM requests, as evidenced by the service layer logic referencing columns such as `id`, `platform`, `label`, `encrypted_key`, and `enabled`.

A typical query pattern from the router service:

```typescript
const db = getDb();
const rows = db.prepare(
  `SELECT id, platform, label, encrypted_key 
   FROM api_keys 
   WHERE enabled = 1`
).all();

```

The database schema supports multiple keys per platform with metadata labels and encryption, allowing the router to implement load balancing and failover between different provider credentials stored in the same SQLite file.

## Summary

- **FreeLLMAPI uses SQLite** (file-based or in-memory) for API key storage, configured via the `DATABASE_URL` environment variable.
- **Connection management** follows a singleton pattern through `initDb()` and `getDb()` functions defined in [`server/src/db/index.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/db/index.ts).
- **Driver flexibility** is provided via `defaultFactory`, which prioritizes `better-sqlite3` but falls back to Node.js's native `node:sqlite`.
- **API keys reside** in the `api_keys` table, queried by services such as [`server/src/services/router.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/services/router.ts) using prepared statements.
- **Automatic directory creation** ensures the database file path exists before connection, simplifying deployment workflows.

## Frequently Asked Questions

### Can I use PostgreSQL instead of SQLite with FreeLLMAPI?

The `.env.example` file comments indicate that PostgreSQL connections are theoretically supported via `DATABASE_URL="postgresql://…"`, though the core database abstraction layer in [`server/src/db/index.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/db/index.ts) specifically implements SQLite connection logic with `better-sqlite3` and `node:sqlite` fallbacks.

### Where does FreeLLMAPI store the SQLite database file?

The storage location is determined by the `DATABASE_URL` environment variable. For production deployments, you might use `sqlite:/var/lib/freellmapi/data.db`, while development environments often use `sqlite:./data/dev.sqlite` or `sqlite::memory:` for ephemeral testing.

### What happens if the better-sqlite3 module fails to install?

The `defaultFactory` function catches require errors for `better-sqlite3` and automatically falls back to `nodeSqliteFactory`, which utilizes Node.js's built-in `node:sqlite` module. This ensures the application runs on systems where native module compilation is unavailable, such as certain ARM Android devices or restricted container environments.

### How does FreeLLMAPI handle database migrations?

Migration files reside in `server/src/db/migrations/` and contain SQL statements (including `CREATE TABLE` definitions for the `api_keys` table) that execute during the server initialization phase to ensure schema consistency before the application begins handling requests.