What Database Does FreeLLMAPI Use for API Key Storage?
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.
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:
# 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:
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, 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:
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—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 attempts to load better-sqlite3 first, then falls back to the built-in node:sqlite module:
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:
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_URLenvironment variable. - Connection management follows a singleton pattern through
initDb()andgetDb()functions defined inserver/src/db/index.ts. - Driver flexibility is provided via
defaultFactory, which prioritizesbetter-sqlite3but falls back to Node.js's nativenode:sqlite. - API keys reside in the
api_keystable, queried by services such asserver/src/services/router.tsusing 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →