D1 Database
D1 Database — это serverless SQL база данных, доступная для ваших приложений. Храните структурированные данные, используйте JOIN, транзакции и индексы.
Что такое D1
Заголовок раздела «Что такое D1»D1 предоставляет:
- SQLite совместимость — стандартный SQL, известный синтаксис
- Edge-ready — низкая задержка запросов с edge-нод
- Автоматическое масштабирование — не нужно думать о серверах
- Изоляция по проектам — каждый проект имеет свою базу
REST API
Заголовок раздела «REST API»D1 доступна через REST API с использованием API токена.
Базовый URL
Заголовок раздела «Базовый URL»https://api.onreza.ru/api/d1/databases/{databaseId}databaseId — UUID вашей базы, доступен в настройках проекта.
Аутентификация
Заголовок раздела «Аутентификация»Authorization: Bearer {ONREZA_API_TOKEN}Токен можно создать в Settings → API Keys.
Endpoints
Заголовок раздела «Endpoints»POST /api/d1/databases/:id/query
Заголовок раздела «POST /api/d1/databases/:id/query»Выполняет SQL запрос и возвращает результат в объектном формате (рекомендуется для большинства случаев).
Через JavaScript/TypeScript:
const response = await fetch( `https://api.onreza.ru/api/d1/databases/${databaseId}/query`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ sql: "SELECT * FROM users WHERE email = ?", params: ["user@example.com"] }), });const data = await response.json();Ответ:
{ "success": true, "result": { "results": [ { "id": 1, "name": "Alice", "email": "alice@example.com" } ], "success": true, "meta": { "duration": 0.5, "rows_read": 1, "rows_written": 0 } }, "errors": [], "messages": []}POST /api/d1/databases/:id/raw
Заголовок раздела «POST /api/d1/databases/:id/raw»Выполняет SQL запрос и возвращает результат в колоночном формате (оптимально для больших наборов данных).
Через JavaScript/TypeScript:
const response = await fetch( `https://api.onreza.ru/api/d1/databases/${databaseId}/raw`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ sql: "SELECT * FROM users", params: [] }), });Ответ:
{ "success": true, "result": { "results": { "columns": ["id", "name", "email"], "rows": [ [1, "Alice", "alice@example.com"], [2, "Bob", "bob@example.com"] ] } }}POST /api/d1/databases/:id/batch
Заголовок раздела «POST /api/d1/databases/:id/batch»Выполняет несколько SQL запросов в одной транзакции. Если один запрос не удаётся, откатываются все изменения.
Через JavaScript/TypeScript:
const response = await fetch( `https://api.onreza.ru/api/d1/databases/${databaseId}/batch`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify([ { sql: "INSERT INTO users (name, email) VALUES (?, ?)", params: ["Alice", "alice@example.com"] }, { sql: "INSERT INTO logs (action) VALUES (?)", params: ["user_created"] } ]), });Ответ:
{ "success": true, "result": [ { "results": [], "success": true, "meta": { "duration": 0.3, "rows_read": 0, "rows_written": 1 } }, { "results": [], "success": true, "meta": { "duration": 0.2, "rows_read": 0, "rows_written": 1 } } ], "errors": [], "messages": []}Использование через Runtime SDK
Заголовок раздела «Использование через Runtime SDK»Для доступа к D1 из Edge Functions и Compute Apps используйте Runtime SDK:
npm install @onreza/runtime# или: pnpm add / yarn add / bun addimport { db } from '@onreza/runtime/db';
// Prepare → bind → executeconst { results } = await db.prepare('SELECT * FROM users WHERE email = ?') .bind('alice@example.com') .all();
// Первая строкаconst user = await db.prepare('SELECT * FROM users WHERE id = ?') .bind(1) .first();
// Insertawait db.prepare('INSERT INTO users (name, email) VALUES (?, ?)') .bind('Charlie', 'charlie@example.com') .run();
// Batch (атомарная транзакция)await db.batch([ db.prepare('INSERT INTO orders (user_id, total) VALUES (?, ?)').bind(1, 100), db.prepare('UPDATE users SET order_count = order_count + 1 WHERE id = ?').bind(1),]);
// DDLawait db.exec('CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, message TEXT)');Пример в Compute App (Next.js)
Заголовок раздела «Пример в Compute App (Next.js)»// app/api/users/route.ts (Next.js App Router)import { db } from '@onreza/runtime/db';
export async function GET(request: Request) { const url = new URL(request.url); const email = url.searchParams.get('email');
if (email) { const user = await db.prepare('SELECT * FROM users WHERE email = ?') .bind(email) .first(); return Response.json(user ?? { error: 'Not found' }); }
const { results } = await db.prepare('SELECT * FROM users LIMIT 50').all(); return Response.json(results);}
export async function POST(request: Request) { const { name, email } = await request.json(); await db.prepare('INSERT INTO users (name, email) VALUES (?, ?)') .bind(name, email) .run(); return Response.json({ success: true }, { status: 201 });}D1 + KV: кэширование запросов
Заголовок раздела «D1 + KV: кэширование запросов»Используйте KV Store как кэш перед D1 для снижения нагрузки:
import { db } from '@onreza/runtime/db';import { kv, isKVAvailable } from '@onreza/runtime/kv';
async function getUserById(id: number) { const cacheKey = `cache:user:${id}`;
// 1. Проверяем кэш if (isKVAvailable()) { const cached = await kv.get(cacheKey, { type: 'json' }); if (cached !== null) return cached; }
// 2. Запрос в D1 const user = await db.prepare('SELECT * FROM users WHERE id = ?') .bind(id) .first();
// 3. Сохраняем в кэш (5 минут) if (user && isKVAvailable()) { await kv.set(cacheKey, user, { ttl: 300 }); }
return user;}
// Инвалидация при обновленииasync function updateUser(id: number, name: string) { await db.prepare('UPDATE users SET name = ? WHERE id = ?') .bind(name, id) .run();
// Удаляем устаревший кэш if (isKVAvailable()) { await kv.delete(`cache:user:${id}`); }}REST API
Заголовок раздела «REST API»Для доступа к D1 вне ONREZA (CI/CD, внешние сервисы, локальная разработка) используйте REST API.
JavaScript/TypeScript
Заголовок раздела «JavaScript/TypeScript»// Клиент для D1class D1Client { constructor( private databaseId: string, private token: string ) {}
async query(sql: string, params: unknown[] = []) { const response = await fetch( `https://api.onreza.ru/api/d1/databases/${this.databaseId}/query`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ sql, params }), } );
if (!response.ok) { throw new Error(`D1 error: ${response.status}`); }
const data = await response.json(); if (!data.success) { throw new Error(data.errors?.[0]?.message || 'Unknown error'); }
return data.result; }
async batch(statements: { sql: string; params: unknown[] }[]) { const response = await fetch( `https://api.onreza.ru/api/d1/databases/${this.databaseId}/batch`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(statements), } );
return response.json(); }}
// Использованиеconst db = new D1Client(process.env.D1_DATABASE_ID!, process.env.ONREZA_API_TOKEN!);
// SELECTconst users = await db.query( "SELECT * FROM users WHERE created_at > ?", ["2024-01-01"]);
// INSERTawait db.query( "INSERT INTO users (name, email) VALUES (?, ?)", ["Charlie", "charlie@example.com"]);
// Batch (транзакция)await db.batch([ { sql: "INSERT INTO orders (user_id, total) VALUES (?, ?)", params: [1, 100] }, { sql: "UPDATE users SET order_count = order_count + 1 WHERE id = ?", params: [1] },]);Типы данных
Заголовок раздела «Типы данных»| SQL тип | JavaScript | Примечание |
|---|---|---|
INTEGER | number | 64-bit signed integer |
REAL | number | 64-bit float |
TEXT | string | UTF-8 строка |
BLOB | Uint8Array или { $blob: base64 } | Бинарные данные |
NULL | null |
BLOB Binary I/O
Заголовок раздела «BLOB Binary I/O»D1 поддерживает работу с бинарными данными через механизм $blob JSON marker. Это позволяет сохранять и извлекать бинарные данные (изображения, файлы, сериализованные данные) без потери формата.
Запись бинарных данных
Заголовок раздела «Запись бинарных данных»При отправке запроса используйте $blob с base64-кодированными данными:
// Запись бинарных данных через $blob markerconst imageBuffer = await fs.readFile('./avatar.png');const base64 = imageBuffer.toString('base64');
await db.query( "INSERT INTO attachments (id, data) VALUES (?, ?)", [1, { $blob: base64 }]);
// Или с использованием Uint8Array (автоматически сериализуется)const binaryData = new Uint8Array([0x89, 0x50, 0x4E, 0x47]); // PNG headerawait db.query( "INSERT INTO files (id, content) VALUES (?, ?)", [2, binaryData]);Чтение бинарных данных
Заголовок раздела «Чтение бинарных данных»При чтении BLOB-данных возвращается объект с $blob маркером:
// Чтение — вернётся как объект с $blobconst result = await db.query( "SELECT data FROM attachments WHERE id = ?", [1]);
// result.results[0].data будет:// { $blob: "iVBORw0KGgoAAAANSUhEUgAA..." }
// Декодирование base64 в Uint8Arrayfunction decodeBlob(blobObj: { $blob: string }): Uint8Array { const binaryString = atob(blobObj.$blob); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes;}
const imageData = decodeBlob(result.results[0].data);Batch операции с BLOB
Заголовок раздела «Batch операции с BLOB»await db.batch([ { sql: "INSERT INTO documents (id, pdf) VALUES (?, ?)", params: [1, { $blob: pdfBase64 }] }, { sql: "INSERT INTO thumbnails (doc_id, image) VALUES (?, ?)", params: [1, { $blob: thumbnailBase64 }] }]);Безопасность
Заголовок раздела «Безопасность»D1 реализует многоуровневую систему безопасности для защиты ваших данных и инфраструктуры.
Встроенная защита
Заголовок раздела «Встроенная защита»D1 автоматически блокирует потенциально опасные SQL-операции:
| Заблокированная команда | Причина |
|---|---|
ATTACH DATABASE | Предотвращает доступ к другим базам данных |
DETACH DATABASE | Защита от отключения основной базы |
PRAGMA (кроме разрешённых) | Блокировка изменения настроек SQLite |
При попытке выполнить заблокированную команду вы получите ошибку:
{ "success": false, "errors": [ { "message": "SQL command 'ATTACH' is not allowed" } ]}Ограничения на уровне запросов
Заголовок раздела «Ограничения на уровне запросов»| Параметр | Лимит |
|---|---|
| Максимальная длина SQL | 1 MB |
| Строк в ответе | 5,000 |
| Параметров в запросе | 100 |
| Запросов в batch | 100 |
| Таймаут запроса | 10 сек |
| Таймаут batch | 30 сек |
| Максимальный размер ответа | 10 MB |
Prepared Statements
Заголовок раздела «Prepared Statements»Всегда используйте параметризованные запросы для предотвращения SQL-инъекций:
// Неправильно — SQL injection рискawait db.query(`SELECT * FROM users WHERE email = '${email}'`);
// Правильно — параметризованный запросawait db.query("SELECT * FROM users WHERE email = ?", [email]);Тарифные лимиты
Заголовок раздела «Тарифные лимиты»| Метрика | Starter | Pro |
|---|---|---|
| Storage | 500 MB | 5 GB |
| Rows Read/месяц | 5M | 50M |
| Rows Written/месяц | 1M | 10M |
При превышении на Starter — операции блокируются. На Pro — начисляется оверадж. Подробнее: Лимиты и квоты.
Технические лимиты
Заголовок раздела «Технические лимиты»| Параметр | Лимит |
|---|---|
| Максимальная длина SQL | 1 MB |
| Максимальное количество строк в результате | 5,000 |
| Максимум параметров | 100 |
| Максимум запросов в batch | 100 |
| Таймаут одиночного запроса | 10 сек |
| Таймаут batch | 30 сек |
| Максимальный размер ответа | 10 MB |
Лучшие практики
Заголовок раздела «Лучшие практики»Batch для атомарных операций
Заголовок раздела «Batch для атомарных операций»// Перевод денег — обе операции или ни однаawait db.batch([ { sql: "UPDATE accounts SET balance = balance - ? WHERE id = ?", params: [100, fromAccountId] }, { sql: "UPDATE accounts SET balance = balance + ? WHERE id = ?", params: [100, toAccountId] }, { sql: "INSERT INTO transactions (from_id, to_id, amount) VALUES (?, ?, ?)", params: [fromAccountId, toAccountId, 100] },]);Индексы
Заголовок раздела «Индексы»-- Создавайте индексы для часто используемых полейCREATE INDEX idx_users_email ON users(email);CREATE INDEX idx_orders_user_id ON orders(user_id);CREATE INDEX idx_orders_created ON orders(created_at);Connection Pooling
Заголовок раздела «Connection Pooling»D1 автоматически управляет соединениями. Просто делайте запросы — не нужно заботиться о пулах.
Пагинация больших результатов
Заголовок раздела «Пагинация больших результатов»// Результат ограничен 5,000 строками — используйте LIMIT/OFFSETasync function* paginateUsers(batchSize = 1000) { let offset = 0; while (true) { const rows = await db.query( "SELECT * FROM users LIMIT ? OFFSET ?", [batchSize, offset] ); if (rows.results.length === 0) break; yield rows.results; offset += batchSize; }}
// Использованиеfor await (const batch of paginateUsers()) { for (const user of batch) { console.log(user); }}Schema миграция
Заголовок раздела «Schema миграция»Создание таблиц
Заголовок раздела «Создание таблиц»await db.query(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, name TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP )`);
await db.query(` CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL, content TEXT, published BOOLEAN DEFAULT FALSE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) )`);Миграции
Заголовок раздела «Миграции»Рекомендуется хранить миграции в виде SQL-файлов и применять при деплое:
// migrations/002_add_indexes.sql
async function migrate() { const files = await fs.readdir('./migrations'); const sqlFiles = files.filter(f => f.endsWith('.sql')).sort();
for (const file of sqlFiles) { const sql = await fs.readFile(`./migrations/${file}`, 'utf-8'); await db.query(sql); console.log(`Applied: ${file}`); }}Troubleshooting
Заголовок раздела «Troubleshooting»”SQLITE_ERROR: no such table”
Заголовок раздела «”SQLITE_ERROR: no such table”»Таблица не существует. Проверьте что миграции применены.
”SQLITE_CONSTRAINT_UNIQUE”
Заголовок раздела «”SQLITE_CONSTRAINT_UNIQUE”»Нарушение уникального ограничения. Проверьте данные перед INSERT.
”Query timeout”
Заголовок раздела «”Query timeout”»Запрос выполняется дольше 10 секунд. Добавьте индексы или оптимизируйте запрос.
”Batch size exceeded”
Заголовок раздела «”Batch size exceeded”»В batch больше 100 запросов. Разбейте на несколько batch-ей.
”Result rows limit exceeded”
Заголовок раздела «”Result rows limit exceeded”»Результат содержит более 5000 строк. Используйте LIMIT и OFFSET для пагинации.
”SQL command is not allowed”
Заголовок раздела «”SQL command is not allowed”»Вы пытаетесь выполнить заблокированную команду (ATTACH, DETACH, PRAGMA). Эти команды запрещены для безопасности.
См. также
Заголовок раздела «См. также»- Runtime SDK — полный API reference
- KV Store — key-value хранилище
- API Tokens — создание токенов для API
- Environment Variables — хранение DATABASE_ID