Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
451 lines
12 KiB
Markdown
451 lines
12 KiB
Markdown
# Turso & libSQL: SQLite für die Edge
|
||
|
||
**Meta-Description:** Turso als Edge-Hosted SQLite mit libSQL. Embedded Replicas, Vector Search und Local-First Development.
|
||
|
||
**Keywords:** Turso, libSQL, SQLite Edge, Embedded Replicas, Local-First, Edge Database, Distributed SQLite
|
||
|
||
---
|
||
|
||
## Einführung
|
||
|
||
Turso bringt **SQLite in die Edge**. Basierend auf libSQL (einem SQLite-Fork) bietet es embedded Replicas, globale Verteilung und native Vector Search – perfekt für Low-Latency Anwendungen weltweit.
|
||
|
||
---
|
||
|
||
## Turso Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ TURSO ARCHITECTURE │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Primary Database (Write): │
|
||
│ └── Zentraler Write-Node │
|
||
│ │
|
||
│ Edge Replicas (Read): │
|
||
│ ├── Frankfurt │
|
||
│ ├── New York │
|
||
│ ├── Singapore │
|
||
│ └── São Paulo │
|
||
│ └── Automatische Synchronisation │
|
||
│ │
|
||
│ Embedded Replicas (On-Device): │
|
||
│ ├── In-App SQLite Kopie │
|
||
│ ├── Offline-fähig │
|
||
│ └── Sync bei Reconnect │
|
||
│ │
|
||
│ Features: │
|
||
│ ├── libSQL (SQLite Fork) │
|
||
│ ├── Native Vector Search │
|
||
│ ├── Branching (wie Git) │
|
||
│ └── MCP Server für AI Assistants │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Setup
|
||
|
||
```bash
|
||
# Turso CLI installieren
|
||
curl -sSfL https://get.tur.so/install.sh | bash
|
||
|
||
# Login
|
||
turso auth login
|
||
|
||
# Database erstellen
|
||
turso db create my-app --location fra # Frankfurt
|
||
|
||
# Replicas hinzufügen
|
||
turso db replicas add my-app --location iad # US East
|
||
turso db replicas add my-app --location sin # Singapore
|
||
|
||
# Connection URL und Token
|
||
turso db show my-app --url
|
||
turso db tokens create my-app
|
||
```
|
||
|
||
```bash
|
||
# Node.js Client installieren
|
||
npm install @libsql/client
|
||
```
|
||
|
||
---
|
||
|
||
## Basic Connection
|
||
|
||
```typescript
|
||
// lib/turso.ts
|
||
import { createClient } from '@libsql/client';
|
||
|
||
export const turso = createClient({
|
||
url: process.env.TURSO_DATABASE_URL!,
|
||
authToken: process.env.TURSO_AUTH_TOKEN!
|
||
});
|
||
|
||
// Für lokale Entwicklung (SQLite File)
|
||
export const localDb = createClient({
|
||
url: 'file:local.db'
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## CRUD Operations
|
||
|
||
```typescript
|
||
import { turso } from '@/lib/turso';
|
||
|
||
// CREATE TABLE
|
||
async function initializeSchema() {
|
||
await turso.execute(`
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
email TEXT UNIQUE NOT NULL,
|
||
name TEXT,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
`);
|
||
|
||
await turso.execute(`
|
||
CREATE TABLE IF NOT EXISTS posts (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
title TEXT NOT NULL,
|
||
content TEXT,
|
||
author_id INTEGER NOT NULL,
|
||
published INTEGER DEFAULT 0,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
FOREIGN KEY (author_id) REFERENCES users(id)
|
||
)
|
||
`);
|
||
}
|
||
|
||
// CREATE
|
||
async function createUser(email: string, name: string) {
|
||
const result = await turso.execute({
|
||
sql: 'INSERT INTO users (email, name) VALUES (?, ?) RETURNING *',
|
||
args: [email, name]
|
||
});
|
||
return result.rows[0];
|
||
}
|
||
|
||
// READ
|
||
async function getUserById(id: number) {
|
||
const result = await turso.execute({
|
||
sql: 'SELECT * FROM users WHERE id = ?',
|
||
args: [id]
|
||
});
|
||
return result.rows[0];
|
||
}
|
||
|
||
async function getPostsWithAuthors() {
|
||
const result = await turso.execute(`
|
||
SELECT
|
||
p.id,
|
||
p.title,
|
||
p.content,
|
||
p.created_at,
|
||
u.name as author_name,
|
||
u.email as author_email
|
||
FROM posts p
|
||
JOIN users u ON p.author_id = u.id
|
||
WHERE p.published = 1
|
||
ORDER BY p.created_at DESC
|
||
`);
|
||
return result.rows;
|
||
}
|
||
|
||
// UPDATE
|
||
async function updatePost(id: number, title: string, content: string) {
|
||
const result = await turso.execute({
|
||
sql: 'UPDATE posts SET title = ?, content = ? WHERE id = ? RETURNING *',
|
||
args: [title, content, id]
|
||
});
|
||
return result.rows[0];
|
||
}
|
||
|
||
// DELETE
|
||
async function deletePost(id: number) {
|
||
await turso.execute({
|
||
sql: 'DELETE FROM posts WHERE id = ?',
|
||
args: [id]
|
||
});
|
||
}
|
||
|
||
// BATCH (Transaction)
|
||
async function createUserWithPosts(
|
||
user: { email: string; name: string },
|
||
posts: { title: string; content: string }[]
|
||
) {
|
||
const result = await turso.batch([
|
||
{
|
||
sql: 'INSERT INTO users (email, name) VALUES (?, ?) RETURNING id',
|
||
args: [user.email, user.name]
|
||
},
|
||
...posts.map(post => ({
|
||
sql: 'INSERT INTO posts (title, content, author_id) VALUES (?, ?, last_insert_rowid())',
|
||
args: [post.title, post.content]
|
||
}))
|
||
], 'write');
|
||
|
||
return result;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Embedded Replicas (Local-First)
|
||
|
||
```typescript
|
||
// lib/turso-embedded.ts
|
||
import { createClient } from '@libsql/client';
|
||
|
||
// Embedded Replica mit Sync
|
||
export const db = createClient({
|
||
url: 'file:local-replica.db', // Lokale SQLite Datei
|
||
syncUrl: process.env.TURSO_DATABASE_URL!,
|
||
authToken: process.env.TURSO_AUTH_TOKEN!,
|
||
syncInterval: 60 // Sync alle 60 Sekunden
|
||
});
|
||
|
||
// Manuelle Synchronisation
|
||
async function syncDatabase() {
|
||
await db.sync();
|
||
console.log('Database synced with remote');
|
||
}
|
||
|
||
// Verwendung
|
||
async function getDataWithFallback() {
|
||
try {
|
||
// Versuche lokale Query (schnell!)
|
||
const result = await db.execute('SELECT * FROM posts LIMIT 10');
|
||
return result.rows;
|
||
} catch (error) {
|
||
// Fallback zu Remote bei Fehler
|
||
await db.sync();
|
||
const result = await db.execute('SELECT * FROM posts LIMIT 10');
|
||
return result.rows;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Vector Search
|
||
|
||
```typescript
|
||
// libSQL unterstützt native Vector Operations
|
||
|
||
// Tabelle mit Vector Column
|
||
await turso.execute(`
|
||
CREATE TABLE IF NOT EXISTS documents (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
content TEXT NOT NULL,
|
||
embedding F32_BLOB(1536), -- OpenAI ada-002 Dimension
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
`);
|
||
|
||
// Vector Index erstellen
|
||
await turso.execute(`
|
||
CREATE INDEX IF NOT EXISTS documents_embedding_idx
|
||
ON documents (libsql_vector_idx(embedding))
|
||
`);
|
||
|
||
// Document mit Embedding speichern
|
||
async function saveDocument(content: string, embedding: number[]) {
|
||
await turso.execute({
|
||
sql: `
|
||
INSERT INTO documents (content, embedding)
|
||
VALUES (?, vector32(?))
|
||
`,
|
||
args: [content, JSON.stringify(embedding)]
|
||
});
|
||
}
|
||
|
||
// Similarity Search
|
||
async function searchSimilar(queryEmbedding: number[], limit: number = 5) {
|
||
const result = await turso.execute({
|
||
sql: `
|
||
SELECT
|
||
id,
|
||
content,
|
||
vector_distance_cos(embedding, vector32(?)) as distance
|
||
FROM documents
|
||
ORDER BY distance ASC
|
||
LIMIT ?
|
||
`,
|
||
args: [JSON.stringify(queryEmbedding), limit]
|
||
});
|
||
|
||
return result.rows.map(row => ({
|
||
id: row.id,
|
||
content: row.content,
|
||
similarity: 1 - (row.distance as number) // Convert distance to similarity
|
||
}));
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Drizzle ORM Integration
|
||
|
||
```typescript
|
||
// drizzle.config.ts
|
||
import { defineConfig } from 'drizzle-kit';
|
||
|
||
export default defineConfig({
|
||
schema: './src/db/schema.ts',
|
||
out: './drizzle',
|
||
dialect: 'turso',
|
||
dbCredentials: {
|
||
url: process.env.TURSO_DATABASE_URL!,
|
||
authToken: process.env.TURSO_AUTH_TOKEN!
|
||
}
|
||
});
|
||
```
|
||
|
||
```typescript
|
||
// src/db/schema.ts
|
||
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
||
|
||
export const users = sqliteTable('users', {
|
||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||
email: text('email').notNull().unique(),
|
||
name: text('name'),
|
||
createdAt: integer('created_at', { mode: 'timestamp' })
|
||
.notNull()
|
||
.$defaultFn(() => new Date())
|
||
});
|
||
|
||
export const posts = sqliteTable('posts', {
|
||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||
title: text('title').notNull(),
|
||
content: text('content'),
|
||
authorId: integer('author_id')
|
||
.notNull()
|
||
.references(() => users.id),
|
||
published: integer('published', { mode: 'boolean' }).default(false),
|
||
createdAt: integer('created_at', { mode: 'timestamp' })
|
||
.notNull()
|
||
.$defaultFn(() => new Date())
|
||
});
|
||
```
|
||
|
||
```typescript
|
||
// src/db/index.ts
|
||
import { drizzle } from 'drizzle-orm/libsql';
|
||
import { createClient } from '@libsql/client';
|
||
import * as schema from './schema';
|
||
|
||
const client = createClient({
|
||
url: process.env.TURSO_DATABASE_URL!,
|
||
authToken: process.env.TURSO_AUTH_TOKEN!
|
||
});
|
||
|
||
export const db = drizzle(client, { schema });
|
||
|
||
// Queries
|
||
import { eq } from 'drizzle-orm';
|
||
|
||
async function getUserWithPosts(userId: number) {
|
||
return await db.query.users.findFirst({
|
||
where: eq(users.id, userId),
|
||
with: {
|
||
posts: true
|
||
}
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Database Branching
|
||
|
||
```bash
|
||
# Branch erstellen (wie Git)
|
||
turso db branch create my-app feature-branch
|
||
|
||
# Branch verwenden
|
||
turso db show my-app/feature-branch --url
|
||
|
||
# Branch mergen (manuell - Schema migrieren)
|
||
turso db branch delete my-app feature-branch
|
||
```
|
||
|
||
```typescript
|
||
// Branch in Code verwenden
|
||
const branchDb = createClient({
|
||
url: process.env.TURSO_BRANCH_URL!, // Feature Branch URL
|
||
authToken: process.env.TURSO_AUTH_TOKEN!
|
||
});
|
||
|
||
// Schema-Änderungen testen
|
||
await branchDb.execute(`
|
||
ALTER TABLE users ADD COLUMN avatar_url TEXT
|
||
`);
|
||
|
||
// Nach Test: Änderungen auf Production anwenden
|
||
```
|
||
|
||
---
|
||
|
||
## Edge Functions Integration
|
||
|
||
```typescript
|
||
// Cloudflare Workers
|
||
export default {
|
||
async fetch(request: Request, env: Env) {
|
||
const db = createClient({
|
||
url: env.TURSO_DATABASE_URL,
|
||
authToken: env.TURSO_AUTH_TOKEN
|
||
});
|
||
|
||
const { pathname } = new URL(request.url);
|
||
|
||
if (pathname === '/api/posts') {
|
||
const result = await db.execute(
|
||
'SELECT * FROM posts WHERE published = 1 ORDER BY created_at DESC LIMIT 10'
|
||
);
|
||
|
||
return new Response(JSON.stringify(result.rows), {
|
||
headers: { 'Content-Type': 'application/json' }
|
||
});
|
||
}
|
||
|
||
return new Response('Not Found', { status: 404 });
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## Fazit
|
||
|
||
Turso bietet:
|
||
|
||
1. **Global Edge Distribution**: Replicas weltweit für Low-Latency
|
||
2. **Embedded Replicas**: Local-First mit automatischem Sync
|
||
3. **SQLite Compatibility**: Bewährte Technologie, moderne Distribution
|
||
4. **Native Vector Search**: AI-ready ohne externe Services
|
||
|
||
Ideal für globale Anwendungen mit Offline-Support.
|
||
|
||
---
|
||
|
||
## Bildprompts
|
||
|
||
1. "Globe with database nodes connected at edge locations, global distribution"
|
||
2. "SQLite file syncing between device and cloud, embedded replica concept"
|
||
3. "Local-first application working offline then syncing, connectivity visualization"
|
||
|
||
---
|
||
|
||
## Quellen
|
||
|
||
- [Turso Documentation](https://docs.turso.tech/)
|
||
- [libSQL GitHub](https://github.com/tursodatabase/libsql)
|
||
- [Turso Embedded Replicas](https://turso.tech/blog/local-first-cloud-connected-sqlite-with-turso-embedded-replicas)
|
||
- [Turso Vector Search](https://docs.turso.tech/features/vector-search)
|