tgdb-store
v1.1.0
Published
Use a Telegram channel as a database via the Bot API (token + channel)
Downloads
2,053
Maintainers
Readme
TgDb — Telegram DB via Bot API
Store your app data directly in a Telegram channel using a bot.
What this gives you
- One lightweight storage layer without a separate DB server.
- Full row payloads live in downloadable JSON documents (
tgdb-data-*.json), not duplicated as extra channel messages. - Automatic recovery on restart — after
connect(),get()returns the same JSON you saved (seeexamples/restart-recovery.ts). - No local files needed.
- No duplicate uploads — multiple operations in one tick are batched into a single save per table + schema pin.
How data is stored
Files in the Telegram channel
| File | Caption | Pinned | Purpose |
|------|---------|--------|---------|
| tgdb-schema.json | [TGDB:SCHEMA] | Yes | Master index — table list + file_id of each table snapshot |
| tgdb-data-{name}.json | [TGDB:DATA:{name}] | No | Full table snapshot: key → JSON value (v2) |
Ordinary tables do not post per-row text messages. The channel stays mostly documents + optional audit lines (see auditChannelMessages).
tgdb-schema.json (pinned — entry point on restart)
{
"version": 1,
"updatedAt": "2026-05-10T14:00:00.000Z",
"tables": {
"users": {
"rows": 2,
"updatedAt": "2026-05-10T14:00:00.000Z",
"dataFileId": "BQACAgIA...",
"dataMessageId": 510
}
},
"mediaDatabases": {}
}tgdb-data-{name}.json (v2 — canonical)
{
"version": 2,
"table": "users",
"updatedAt": "2026-05-10T14:00:00.000Z",
"rows": {
"u_1": { "id": "u_1", "name": "Alice", "balance": 1200 },
"u_2": { "id": "u_2", "name": "Bob", "balance": 600 }
}
}Each table fits in one JSON document (Telegram document limit ~50 MB upload).
Update rule — batched
Every add / edit / delete / upsert:
- Updates in-memory state.
- Marks the table dirty.
- After the current tick, one flush per dirty table:
- Uploads a fresh
tgdb-data-{name}.json. - Uploads a fresh
tgdb-schema.jsonand pins it. - Deletes the previous data-file message for that table (and the previous schema message).
- Uploads a fresh
addMany / deleteMany still produce one data-file upload and one schema upload per batch.
Restart recovery
On connect():
- Reads the pinned message →
file_idoftgdb-schema.json. - Downloads the schema → table list and
dataFileIdper table. - Downloads each
tgdb-data-{name}.json→ restoresrowsas values. get(),exists(),add(),edit(),delete()behave normally.
Legacy v1 data files
Older snapshots used version: 1 with rows mapping keys to Telegram message_id (not readable back via Bot API). Those keys are loaded as index-only (null payload); exists() is true but get() throws until you edit/upsert the row again. Prefer a fresh channel or re-import data. After any flush with the current library, files are written as v2.
Optional audit messages
By default no [TGDB:ADD], [TGDB:CREATE], etc. text messages are sent. Set auditChannelMessages: true if you want that noise in the channel.
new TelegramDB({ token, channelId, auditChannelMessages: true });Install
npm install tgdb-storeUsage example
import { TelegramDB } from "tgdb-store";
type User = { id: string; name: string; balance: number };
type Product = { id: string; title: string; price: number; stock: number };
async function main() {
const db = new TelegramDB({
token: process.env.BOT_TOKEN!,
channelId: Number(process.env.CHANNEL_ID),
debug: true,
});
await db.connect();
const users = await db.database("users");
const products = await db.database("products");
await users.upsert<User>("u_1", { id: "u_1", name: "Alice", balance: 1200 });
await users.upsert<User>("u_2", { id: "u_2", name: "Bob", balance: 600 });
await products.upsert<Product>("p_1", { id: "p_1", title: "Keyboard", price: 120, stock: 10 });
await products.upsert<Product>("p_2", { id: "p_2", title: "Mouse", price: 40, stock: 25 });
const alice = await users.get<User>("u_1");
const keyboard = await products.get<Product>("p_1");
const qty = 2;
const amount = keyboard.price * qty;
if (keyboard.stock < qty) throw new Error("Not enough stock");
if (alice.balance < amount) throw new Error("Insufficient balance");
await users.edit<User>("u_1", { ...alice, balance: alice.balance - amount });
await products.edit<Product>("p_1", { ...keyboard, stock: keyboard.stock - qty });
await users.addMany<User>({
"u_3": { id: "u_3", name: "Carol", balance: 800 },
"u_4": { id: "u_4", name: "Dan", balance: 300 },
});
console.log(await users.exists("u_1")); // true — also after restart
await users.delete("u_4");
// await db.deleteDatabase("users");
}
main().catch(console.error);Typical channel activity (audit off): only [TGDB:DATA:…] / [TGDB:SCHEMA] document captions as files appear in the chat.
Cross-process recovery example
See examples/restart-recovery.ts. Run after build:
npm run build
BOT_TOKEN=… CHANNEL_ID=… npm run example:restartTests
npm testIntegration test recovery second instance runs only when BOT_TOKEN and CHANNEL_ID are set.
Core API
const db = new TelegramDB({ token, channelId, auditChannelMessages?: false });
await db.connect();
const table = await db.database("tableName");
await table.add("key", value); // add (throws if exists)
await table.get("key"); // read — works after restart (v2 snapshots)
await table.edit("key", value); // overwrite (throws if missing)
await table.upsert("key", value); // add or overwrite
await table.delete("key"); // remove one
await table.exists("key"); // check presence — works after restart
await table.clear(); // remove all rows
await table.addMany({ k1: v1, k2: v2 }); // batch add — one file upload
await table.deleteMany(["k1", "k2"]); // batch delete — one file upload
await table.getRecordIds(); // returns all keys as { key: true }
await db.hasDatabase("tableName"); // check if table exists
await db.deleteDatabase("tableName"); // drop table + delete its data file
await db.mediaDatabase("files"); // binary file storage (separate from JSON tables)
await db.collection("orders"); // collection helperLimits
- Keys allowed:
A-Z,a-z,0-9,-,_. - One JSON document per logical table — practical limit ~50 MB (Telegram upload cap).
- Values must be JSON-serializable (
JSON.stringify). - Media files use
mediaDatabase()(not the JSON row store).
