Turso و Drizzle ORM مع Next.js: قواعد بيانات جاهزة للحوسبة الطرفية في 2026

ما ستتعلمه: كيفية ربط Turso (قاعدة بيانات SQLite موزعة) مع Next.js 15 عبر Drizzle ORM، وإنشاء مخططات آمنة النوع، وتنفيذ عمليات الترحيل، والنشر على Vercel Edge Functions بزمن استجابة أقل من 10 مللي ثانية حول العالم.
المقدمة
تعمل قواعد البيانات التقليدية مثل PostgreSQL و MySQL من مركز بيانات واحد. عندما يستعلم مستخدم في تونس من خادم في باريس، يضيف زمن الشبكة من 30 إلى 80 مللي ثانية لكل استعلام. اضرب ذلك في 5 استعلامات لكل صفحة وستحصل على نصف ثانية من التأخير الذي لا مفر منه.
Turso يحل هذه المشكلة باستخدام LibSQL، وهو fork مفتوح المصدر من SQLite مصمم للتوزيع على الحوسبة الطرفية. يتم نسخ قاعدة بياناتك تلقائيًا عبر عشرات المناطق. مع Drizzle ORM — وهو ORM خفيف وآمن النوع لـ TypeScript — و Edge Functions في Next.js 15، تحصل على حزمة كاملة حيث يُقدَّم كل استعلام من أقرب مركز بيانات للمستخدم.
في هذا الدليل، سنبني تطبيقًا لإدارة الإشارات المرجعية (bookmarks) مع عمليات CRUD كاملة ونشر جاهز للحوسبة الطرفية.
المتطلبات الأساسية
قبل البدء، تأكد من أن لديك:
- Node.js 20+ مثبت (
node --version) - حساب Turso مجاني على turso.tech
- Turso CLI مثبت:
brew install tursodatabase/tap/turso(macOS) أوcurl -sSfL https://get.tur.so/install.sh | bash(Linux) - معرفة أساسية بـ Next.js App Router و TypeScript
- محرر كود (يُنصح بـ VS Code)
ما ستبنيه
تطبيق إشارات مرجعية بالميزات التالية:
- إنشاء وقراءة وتحديث وحذف الإشارات المرجعية
- تصنيف بالوسوم
- بحث نصي كامل
- مسارات API تعمل على Edge Runtime
- زمن استجابة أقل من 10 مللي ثانية بفضل نسخ Turso
الخطوة 1: إنشاء مشروع Next.js
ابدأ بتهيئة مشروع Next.js 15 جديد مع TypeScript:
npx create-next-app@latest turso-bookmarks --typescript --tailwind --eslint --app --src-dir
cd turso-bookmarksاختر الخيارات الافتراضية عندما يطلب منك CLI. يجب أن يكون لديك هيكل مشروع قياسي مع مجلد src/app/.
الخطوة 2: إعداد Turso
المصادقة عبر CLI
سجّل الدخول إلى Turso من الطرفية:
turso auth loginسيفتح هذا المتصفح للمصادقة. بعد تسجيل الدخول، أنشئ قاعدة بياناتك:
turso db create bookmarks-db --group defaultالنسخ التلقائي: ينسخ Turso قاعدة بياناتك تلقائيًا عبر مناطق مجموعتك. تستخدم مجموعة default أقرب منطقة لك. أضف نسخًا إضافية بـ turso db replicate bookmarks-db [region].
الحصول على بيانات الاعتماد
احصل على رابط الاتصال والرمز المميز:
turso db show bookmarks-db --url
turso db tokens create bookmarks-dbأنشئ ملف .env.local في جذر مشروعك:
TURSO_DATABASE_URL=libsql://bookmarks-db-[your-org].turso.io
TURSO_AUTH_TOKEN=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...الأمان: لا تقم أبدًا بإضافة رموز Turso إلى Git. أضف .env.local إلى .gitignore (يفعل Next.js هذا افتراضيًا).
الخطوة 3: تثبيت التبعيات
ثبّت Drizzle ORM مع مشغل LibSQL:
npm install drizzle-orm @libsql/client
npm install -D drizzle-kitإليك ما تفعله كل حزمة:
| الحزمة | الدور |
|---|---|
drizzle-orm | ORM آمن النوع لـ TypeScript |
@libsql/client | مشغل LibSQL لـ Turso |
drizzle-kit | CLI للترحيل والاستبطان |
الخطوة 4: إعداد Drizzle
إعداد عميل قاعدة البيانات
أنشئ ملف الاتصال src/lib/db.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 });إعداد Drizzle Kit
أنشئ drizzle.config.ts في جذر المشروع:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/lib/schema.ts",
out: "./drizzle",
dialect: "turso",
dbCredentials: {
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
},
});لهجة Turso: منذ الإصدار Drizzle Kit v0.22+، يتم دعم لهجة turso أصليًا. وهي تتعامل مع خصوصيات LibSQL مثل أنواع integer للقيم المنطقية و text للتواريخ.
الخطوة 5: تعريف المخطط
أنشئ src/lib/schema.ts مع الجداول لتطبيق الإشارات المرجعية:
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { relations } from "drizzle-orm";
// جدول الإشارات المرجعية
export const bookmarks = sqliteTable("bookmarks", {
id: integer("id").primaryKey({ autoIncrement: true }),
title: text("title").notNull(),
url: text("url").notNull(),
description: text("description"),
favicon: text("favicon"),
createdAt: text("created_at")
.notNull()
.$defaultFn(() => new Date().toISOString()),
updatedAt: text("updated_at")
.notNull()
.$defaultFn(() => new Date().toISOString()),
});
// جدول الوسوم
export const tags = sqliteTable("tags", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull().unique(),
color: text("color").notNull().default("#6366f1"),
});
// جدول الربط بين الإشارات والوسوم
export const bookmarkTags = sqliteTable("bookmark_tags", {
bookmarkId: integer("bookmark_id")
.notNull()
.references(() => bookmarks.id, { onDelete: "cascade" }),
tagId: integer("tag_id")
.notNull()
.references(() => tags.id, { onDelete: "cascade" }),
});
// العلاقات
export const bookmarksRelations = relations(bookmarks, ({ many }) => ({
bookmarkTags: many(bookmarkTags),
}));
export const tagsRelations = relations(tags, ({ many }) => ({
bookmarkTags: many(bookmarkTags),
}));
export const bookmarkTagsRelations = relations(bookmarkTags, ({ one }) => ({
bookmark: one(bookmarks, {
fields: [bookmarkTags.bookmarkId],
references: [bookmarks.id],
}),
tag: one(tags, {
fields: [bookmarkTags.tagId],
references: [tags.id],
}),
}));النقاط الرئيسية في المخطط
- أنواع SQLite: يستخدم LibSQL النوعين
textوintegerكأنواع أساسية. تُخزَّن التواريخ كـtextبصيغة ISO 8601. - علاقات متعدد-بمتعدد: جدول
bookmark_tagsيربط الإشارات المرجعية والوسوم عبر جدول وسيط. - الحذف المتتالي: حذف إشارة مرجعية يحذف تلقائيًا ارتباطات الوسوم الخاصة بها.
- القيم الافتراضية: تُولِّد
$defaultFnالطوابع الزمنية على جانب التطبيق، وليس جانب قاعدة البيانات.
الخطوة 6: تنفيذ عمليات الترحيل
أنشئ ملفات ترحيل SQL من مخططك:
npx drizzle-kit generateيُنشئ هذا مجلد drizzle/ بملفات SQL مرقمة. طبّقها على قاعدة بيانات Turso:
npx drizzle-kit migrateتحقق من إنشاء الجداول:
turso db shell bookmarks-db ".tables"يجب أن ترى: bookmarks، tags، bookmark_tags وجداول ترحيل Drizzle الداخلية.
عمليات الترحيل في الإنتاج: نفّذ دائمًا drizzle-kit generate محليًا وأضف ملفات SQL إلى Git. لا تنفذ أبدًا drizzle-kit push مباشرة في الإنتاج — استخدم drizzle-kit migrate الذي يطبق الملفات المُولَّدة بشكل حتمي.
الخطوة 7: إنشاء مسارات API للحوسبة الطرفية
الميزة الحقيقية لـ Turso تظهر مع Edge Functions. لننشئ مسارات API تعمل على edge runtime.
مسار GET/POST للإشارات المرجعية
أنشئ src/app/api/bookmarks/route.ts:
import { db } from "@/lib/db";
import { bookmarks, bookmarkTags, tags } from "@/lib/schema";
import { eq, like, desc } from "drizzle-orm";
import { NextRequest, NextResponse } from "next/server";
export const runtime = "edge";
// GET /api/bookmarks?search=next&tag=dev
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const search = searchParams.get("search");
const tag = searchParams.get("tag");
let query = db.query.bookmarks.findMany({
with: {
bookmarkTags: {
with: {
tag: true,
},
},
},
orderBy: [desc(bookmarks.createdAt)],
});
const results = await query;
// تصفية في الذاكرة للبحث
let filtered = results;
if (search) {
const term = search.toLowerCase();
filtered = filtered.filter(
(b) =>
b.title.toLowerCase().includes(term) ||
b.description?.toLowerCase().includes(term)
);
}
if (tag) {
filtered = filtered.filter((b) =>
b.bookmarkTags.some((bt) => bt.tag.name === tag)
);
}
return NextResponse.json(filtered);
}
// POST /api/bookmarks
export async function POST(request: NextRequest) {
const body = await request.json();
const { title, url, description, tagIds } = body;
if (!title || !url) {
return NextResponse.json(
{ error: "Title and URL are required" },
{ status: 400 }
);
}
const [bookmark] = await db
.insert(bookmarks)
.values({ title, url, description })
.returning();
// ربط الوسوم
if (tagIds && tagIds.length > 0) {
await db.insert(bookmarkTags).values(
tagIds.map((tagId: number) => ({
bookmarkId: bookmark.id,
tagId,
}))
);
}
return NextResponse.json(bookmark, { status: 201 });
}مسار PUT/DELETE لإشارة مرجعية محددة
أنشئ src/app/api/bookmarks/[id]/route.ts:
import { db } from "@/lib/db";
import { bookmarks, bookmarkTags } from "@/lib/schema";
import { eq } from "drizzle-orm";
import { NextRequest, NextResponse } from "next/server";
export const runtime = "edge";
// PUT /api/bookmarks/:id
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const body = await request.json();
const { title, url, description, tagIds } = body;
const [updated] = await db
.update(bookmarks)
.set({
title,
url,
description,
updatedAt: new Date().toISOString(),
})
.where(eq(bookmarks.id, parseInt(id)))
.returning();
if (!updated) {
return NextResponse.json(
{ error: "Bookmark not found" },
{ status: 404 }
);
}
// تحديث الوسوم
if (tagIds) {
await db
.delete(bookmarkTags)
.where(eq(bookmarkTags.bookmarkId, updated.id));
if (tagIds.length > 0) {
await db.insert(bookmarkTags).values(
tagIds.map((tagId: number) => ({
bookmarkId: updated.id,
tagId,
}))
);
}
}
return NextResponse.json(updated);
}
// DELETE /api/bookmarks/:id
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const [deleted] = await db
.delete(bookmarks)
.where(eq(bookmarks.id, parseInt(id)))
.returning();
if (!deleted) {
return NextResponse.json(
{ error: "Bookmark not found" },
{ status: 404 }
);
}
return NextResponse.json({ success: true });
}مسار الوسوم
أنشئ src/app/api/tags/route.ts:
import { db } from "@/lib/db";
import { tags } from "@/lib/schema";
import { NextRequest, NextResponse } from "next/server";
export const runtime = "edge";
export async function GET() {
const allTags = await db.select().from(tags);
return NextResponse.json(allTags);
}
export async function POST(request: NextRequest) {
const { name, color } = await request.json();
if (!name) {
return NextResponse.json(
{ error: "Tag name is required" },
{ status: 400 }
);
}
const [tag] = await db
.insert(tags)
.values({ name, color: color || "#6366f1" })
.returning();
return NextResponse.json(tag, { status: 201 });
}الخطوة 8: إنشاء Server Component الرئيسي
أنشئ الصفحة الرئيسية في src/app/page.tsx التي تستخدم Server Components للعرض الأولي:
import { db } from "@/lib/db";
import { bookmarks, tags } from "@/lib/schema";
import { desc } from "drizzle-orm";
import { BookmarkList } from "@/components/bookmark-list";
export const runtime = "edge";
export default async function HomePage() {
const allBookmarks = await db.query.bookmarks.findMany({
with: {
bookmarkTags: {
with: {
tag: true,
},
},
},
orderBy: [desc(bookmarks.createdAt)],
});
const allTags = await db.select().from(tags);
return (
<main className="mx-auto max-w-4xl px-4 py-8">
<h1 className="mb-8 text-3xl font-bold">إشاراتي المرجعية</h1>
<BookmarkList
initialBookmarks={allBookmarks}
tags={allTags}
/>
</main>
);
}Server Component + Edge: بإضافة export const runtime = "edge" إلى الصفحة، يُشغّل Next.js مكون الخادم في Edge Runtime. يُنفَّذ استعلام Drizzle إلى Turso من أقرب مركز بيانات طرفي — مما يؤدي إلى Time to First Byte فائق السرعة.
الخطوة 9: إنشاء Client Component
أنشئ src/components/bookmark-list.tsx للجزء التفاعلي:
"use client";
import { useState, useTransition } from "react";
type Tag = {
id: number;
name: string;
color: string;
};
type BookmarkTag = {
bookmarkId: number;
tagId: number;
tag: Tag;
};
type Bookmark = {
id: number;
title: string;
url: string;
description: string | null;
favicon: string | null;
createdAt: string;
updatedAt: string;
bookmarkTags: BookmarkTag[];
};
export function BookmarkList({
initialBookmarks,
tags,
}: {
initialBookmarks: Bookmark[];
tags: Tag[];
}) {
const [bookmarksList, setBookmarks] = useState(initialBookmarks);
const [search, setSearch] = useState("");
const [selectedTag, setSelectedTag] = useState<string | null>(null);
const [isPending, startTransition] = useTransition();
const filteredBookmarks = bookmarksList.filter((b) => {
const matchesSearch =
!search ||
b.title.toLowerCase().includes(search.toLowerCase()) ||
b.description?.toLowerCase().includes(search.toLowerCase());
const matchesTag =
!selectedTag ||
b.bookmarkTags.some((bt) => bt.tag.name === selectedTag);
return matchesSearch && matchesTag;
});
async function handleDelete(id: number) {
startTransition(async () => {
const res = await fetch(`/api/bookmarks/${id}`, {
method: "DELETE",
});
if (res.ok) {
setBookmarks((prev) => prev.filter((b) => b.id !== id));
}
});
}
return (
<div>
{/* شريط البحث */}
<div className="mb-6 flex gap-4">
<input
type="text"
placeholder="بحث..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="flex-1 rounded-lg border px-4 py-2"
/>
<select
value={selectedTag || ""}
onChange={(e) => setSelectedTag(e.target.value || null)}
className="rounded-lg border px-4 py-2"
>
<option value="">جميع الوسوم</option>
{tags.map((tag) => (
<option key={tag.id} value={tag.name}>
{tag.name}
</option>
))}
</select>
</div>
{/* قائمة الإشارات المرجعية */}
<div className="space-y-4">
{filteredBookmarks.map((bookmark) => (
<div
key={bookmark.id}
className="rounded-lg border p-4 transition-shadow hover:shadow-md"
>
<div className="flex items-start justify-between">
<div>
<a
href={bookmark.url}
target="_blank"
rel="noopener noreferrer"
className="text-lg font-semibold text-blue-600 hover:underline"
>
{bookmark.title}
</a>
{bookmark.description && (
<p className="mt-1 text-gray-600">
{bookmark.description}
</p>
)}
<div className="mt-2 flex gap-2">
{bookmark.bookmarkTags.map((bt) => (
<span
key={bt.tagId}
className="rounded-full px-2 py-1 text-xs text-white"
style={{
backgroundColor: bt.tag.color,
}}
>
{bt.tag.name}
</span>
))}
</div>
</div>
<button
onClick={() => handleDelete(bookmark.id)}
disabled={isPending}
className="text-red-500 hover:text-red-700"
>
حذف
</button>
</div>
</div>
))}
</div>
{filteredBookmarks.length === 0 && (
<p className="text-center text-gray-500">
لم يتم العثور على إشارات مرجعية.
</p>
)}
</div>
);
}الخطوة 10: إضافة سكريبت البذر
لاختبار تطبيقنا، أنشئ سكريبت بذر في src/lib/seed.ts:
import { db } from "./db";
import { bookmarks, tags, bookmarkTags } from "./schema";
async function seed() {
console.log("Seeding database...");
// إنشاء الوسوم
const [devTag] = await db
.insert(tags)
.values({ name: "dev", color: "#6366f1" })
.returning();
const [designTag] = await db
.insert(tags)
.values({ name: "design", color: "#ec4899" })
.returning();
const [toolsTag] = await db
.insert(tags)
.values({ name: "tools", color: "#14b8a6" })
.returning();
// إنشاء الإشارات المرجعية
const [bm1] = await db
.insert(bookmarks)
.values({
title: "Next.js Documentation",
url: "https://nextjs.org/docs",
description: "التوثيق الرسمي لـ Next.js",
})
.returning();
const [bm2] = await db
.insert(bookmarks)
.values({
title: "Turso Documentation",
url: "https://docs.turso.tech",
description: "الدليل الشامل لـ Turso و LibSQL",
})
.returning();
const [bm3] = await db
.insert(bookmarks)
.values({
title: "Drizzle ORM",
url: "https://orm.drizzle.team",
description: "ORM بدون واجهة لـ TypeScript لـ SQL",
})
.returning();
// ربط الوسوم بالإشارات المرجعية
await db.insert(bookmarkTags).values([
{ bookmarkId: bm1.id, tagId: devTag.id },
{ bookmarkId: bm2.id, tagId: devTag.id },
{ bookmarkId: bm2.id, tagId: toolsTag.id },
{ bookmarkId: bm3.id, tagId: devTag.id },
{ bookmarkId: bm3.id, tagId: toolsTag.id },
]);
console.log("Seed completed!");
}
seed().catch(console.error);نفّذ سكريبت البذر:
npx tsx src/lib/seed.tsالخطوة 11: تحسين أداء الحوسبة الطرفية
الاتصالات المستمرة
يدير عميل LibSQL تلقائيًا مجموعة اتصالات HTTP. بالنسبة لـ Edge Functions (التي هي عديمة الحالة)، ينشئ كل استدعاء اتصالًا جديدًا. يُحسّن Turso هذا باتصالات HTTP/2 المتعددة.
التخزين المؤقت مع Next.js
استخدم التخزين المؤقت لـ Next.js للبيانات التي نادرًا ما تتغير:
import { unstable_cache } from "next/cache";
export const getCachedTags = unstable_cache(
async () => {
return db.select().from(tags);
},
["all-tags"],
{
revalidate: 3600, // ساعة واحدة
tags: ["tags"],
}
);النسخ المدمجة (اختياري)
لزمن استجابة أقل في التطوير المحلي، يدعم Turso النسخ المدمجة — نسخة SQLite محلية تتزامن تلقائيًا:
import { createClient } from "@libsql/client";
const client = createClient({
url: "file:local-replica.db",
syncUrl: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
syncInterval: 60, // مزامنة كل 60 ثانية
});النسخ المدمجة و Edge: لا تعمل النسخ المدمجة في Edge Runtime (لا يوجد نظام ملفات). استخدمها فقط للتطوير المحلي أو خوادم Node.js التقليدية.
الخطوة 12: النشر على Vercel
إعداد متغيرات البيئة
في لوحة تحكم Vercel، أضف المتغيرات التالية:
TURSO_DATABASE_URL— رابط قاعدة بيانات TursoTURSO_AUTH_TOKEN— الرمز المُولَّد بـturso db tokens create
إعداد Edge Runtime
تأكد من أن مسارات API والصفحات تستخدم export const runtime = "edge". سينشر Vercel تلقائيًا هذه الوظائف على شبكته الطرفية العالمية.
النشر
npx vercel deploy --prodأو اربط مستودع Git للنشر التلقائي مع كل push.
التحقق من الأداء
بعد النشر، اختبر زمن الاستجابة من مناطق مختلفة باستخدام curl:
curl -o /dev/null -s -w "Time: %{time_total}s\n" \
https://your-app.vercel.app/api/bookmarksيجب أن تلاحظ أوقات استجابة أقل من 50 مللي ثانية من معظم المناطق، حيث يكون معظمها بسبب مصافحة TLS. يستغرق استعلام Turso نفسه عادة بين 1 و 8 مللي ثانية.
الخطوة 13: إضافة البحث النصي الكامل
يدعم Turso البحث النصي الكامل عبر FTS5، وهو امتداد SQLite. لنضفه إلى تطبيقنا.
إنشاء جدول FTS
أنشئ ملف ترحيل يدوي drizzle/fts-setup.sql:
CREATE VIRTUAL TABLE IF NOT EXISTS bookmarks_fts USING fts5(
title,
description,
content='bookmarks',
content_rowid='id'
);
-- مشغلات لإبقاء FTS متزامنًا
CREATE TRIGGER IF NOT EXISTS bookmarks_ai AFTER INSERT ON bookmarks BEGIN
INSERT INTO bookmarks_fts(rowid, title, description)
VALUES (new.id, new.title, new.description);
END;
CREATE TRIGGER IF NOT EXISTS bookmarks_ad AFTER DELETE ON bookmarks BEGIN
INSERT INTO bookmarks_fts(bookmarks_fts, rowid, title, description)
VALUES('delete', old.id, old.title, old.description);
END;
CREATE TRIGGER IF NOT EXISTS bookmarks_au AFTER UPDATE ON bookmarks BEGIN
INSERT INTO bookmarks_fts(bookmarks_fts, rowid, title, description)
VALUES('delete', old.id, old.title, old.description);
INSERT INTO bookmarks_fts(rowid, title, description)
VALUES (new.id, new.title, new.description);
END;طبّق هذا الترحيل يدويًا عبر shell الخاص بـ Turso:
turso db shell bookmarks-db < drizzle/fts-setup.sqlاستعلام البحث
أضف مسار بحث في src/app/api/search/route.ts:
import { db } from "@/lib/db";
import { sql } from "drizzle-orm";
import { NextRequest, NextResponse } from "next/server";
export const runtime = "edge";
export async function GET(request: NextRequest) {
const query = new URL(request.url).searchParams.get("q");
if (!query) {
return NextResponse.json([]);
}
const results = await db.all(
sql`SELECT b.*, bm.rank
FROM bookmarks_fts bm
JOIN bookmarks b ON b.id = bm.rowid
WHERE bookmarks_fts MATCH ${query}
ORDER BY bm.rank
LIMIT 20`
);
return NextResponse.json(results);
}الاختبار والتحقق
للتحقق من أن كل شيء يعمل:
-
شغّل خادم التطوير:
npm run dev -
اختبر مسارات API:
# عرض الإشارات المرجعية curl http://localhost:3000/api/bookmarks # إنشاء إشارة مرجعية curl -X POST http://localhost:3000/api/bookmarks \ -H "Content-Type: application/json" \ -d '{"title":"Test","url":"https://example.com"}' # بحث curl "http://localhost:3000/api/search?q=next" -
تحقق من البيانات في Turso:
turso db shell bookmarks-db "SELECT * FROM bookmarks"
حل المشاكل الشائعة
خطأ "TURSO_DATABASE_URL is not defined"
تأكد من أن ملف .env.local موجود في جذر المشروع وأنك أعدت تشغيل خادم التطوير بعد إنشائه.
خطأ "Token expired"
تنتهي صلاحية رموز Turso. أعد إنشاء واحد بـ:
turso db tokens create bookmarks-db --expiration noneخطأ ترحيل "table already exists"
إذا نفذت عمليات الترحيل عدة مرات، احذف الجداول وأعد التنفيذ:
turso db shell bookmarks-db "DROP TABLE IF EXISTS bookmark_tags; DROP TABLE IF EXISTS bookmarks; DROP TABLE IF EXISTS tags;"
npx drizzle-kit migrateEdge Runtime "Module not found"
بعض حزم Node.js غير متوافقة مع Edge Runtime. تحقق من أنك تستخدم فقط @libsql/client (الذي يدعم HTTP) وليس better-sqlite3 أو أي مشغل أصلي آخر.
الخطوات التالية
لديك الآن تطبيق كامل مع قاعدة بيانات جاهزة للحوسبة الطرفية. إليك بعض الأفكار للمضي قدمًا:
- إضافة المصادقة مع Better Auth أو Auth.js
- تنفيذ نظام مفضلات مع تحديثات متفائلة
- إضافة Server Actions للتعديلات من جانب الخادم بدون مسارات API
- إعداد المراقبة مع OpenTelemetry
- بناء إضافة متصفح تحفظ الإشارات المرجعية مباشرة
الخلاصة
في هذا الدليل، بنينا تطبيق إشارات مرجعية جاهز للحوسبة الطرفية من خلال الجمع بين ثلاث تقنيات قوية:
- Turso (LibSQL) لقاعدة بيانات SQLite موزعة مع نسخ تلقائي
- Drizzle ORM لاستعلامات TypeScript آمنة النوع وعمليات ترحيل تصريحية
- Next.js 15 Edge Runtime لتنفيذ الكود في أقرب نقطة ممكنة من المستخدمين
هذه الحزمة مثالية للتطبيقات التي تتطلب أوقات استجابة فائقة السرعة حول العالم. قاعدة البيانات تسافر مع كودك — لم يعد عليك الاختيار بين الأداء والبساطة.
الكود الكامل لهذا الدليل يُعتبر نقطة انطلاق لمشاريعك الخاصة. يمثل مزيج Turso + Drizzle + Next.js Edge واحدة من أكثر المعماريات أداءً وراحة لتطوير الويب الحديث في 2026.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

بناء تطبيق متكامل باستخدام Drizzle ORM و Next.js 15: قاعدة بيانات آمنة الأنواع من الصفر إلى الإنتاج
تعلّم كيفية بناء تطبيق متكامل آمن الأنواع باستخدام Drizzle ORM مع Next.js 15. يغطي هذا الدليل العملي تصميم المخططات والترحيلات وServer Actions وعمليات CRUD والنشر مع PostgreSQL.

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
تعلم كيف تبني وكيل ذكاء اصطناعي يقرر بشكل مستقل متى وكيف يسترجع المعلومات من قواعد البيانات المتجهية. دليل عملي شامل باستخدام Vercel AI SDK و Next.js مع أمثلة قابلة للتنفيذ.

بناء محرك بحث دلالي بالذكاء الاصطناعي مع Next.js 15 و OpenAI و Pinecone
تعلّم كيف تبني محرك بحث دلالي متقدّم باستخدام Next.js 15 و OpenAI Embeddings و قاعدة بيانات Pinecone المتجهية. يغطي هذا الدليل الشامل من الإعداد إلى النشر مع Server Actions وواجهة بحث تفاعلية.