Turso et Drizzle ORM avec Next.js : bases de données edge-ready en 2026

AI Bot
Par AI Bot ·

Chargement du lecteur de synthèse vocale...

Ce que vous allez apprendre : Comment connecter Turso (une base de données SQLite distribuée) à Next.js 15 via Drizzle ORM, créer des schémas type-safe, exécuter des migrations, et déployer sur Vercel Edge Functions pour des latences inférieures à 10 ms partout dans le monde.

Introduction

Les bases de données traditionnelles (PostgreSQL, MySQL) fonctionnent depuis un seul datacenter. Quand un utilisateur à Tunis requête un serveur à Paris, la latence réseau ajoute 30 à 80 ms à chaque requête. Multipliez par 5 requêtes par page et vous obtenez un demi-seconde de délai incompressible.

Turso résout ce problème avec LibSQL, un fork open-source de SQLite conçu pour la distribution edge. Votre base de données est répliquée automatiquement dans des dizaines de régions. Combiné avec Drizzle ORM — un ORM TypeScript léger et type-safe — et les Edge Functions de Next.js 15, vous obtenez une stack complète où chaque requête est servie depuis le datacenter le plus proche de votre utilisateur.

Dans ce tutoriel, nous allons construire une application de gestion de bookmarks avec authentification, CRUD complet et déploiement edge-ready.

Prérequis

Avant de commencer, assurez-vous que vous avez :

  • Node.js 20+ installé (node --version)
  • Un compte Turso gratuit sur turso.tech
  • Le CLI Turso installé : brew install tursodatabase/tap/turso (macOS) ou curl -sSfL https://get.tur.so/install.sh | bash (Linux)
  • Connaissances de base en Next.js App Router et TypeScript
  • Un éditeur de code (VS Code recommandé)

Ce que vous allez construire

Une application de bookmarks avec les fonctionnalités suivantes :

  • Création, lecture, mise à jour et suppression de bookmarks
  • Catégorisation par tags
  • Recherche full-text
  • API Routes fonctionnant en Edge Runtime
  • Latence inférieure à 10 ms grâce à la réplication Turso

Étape 1 : Créer le projet Next.js

Commencez par initialiser un nouveau projet Next.js 15 avec TypeScript :

npx create-next-app@latest turso-bookmarks --typescript --tailwind --eslint --app --src-dir
cd turso-bookmarks

Sélectionnez les options par défaut quand le CLI vous le demande. Vous devriez avoir une structure de projet standard avec le dossier src/app/.

Étape 2 : Configurer Turso

Authentification CLI

Connectez-vous à Turso depuis votre terminal :

turso auth login

Cela ouvrira votre navigateur pour authentification. Une fois connecté, créez votre base de données :

turso db create bookmarks-db --group default

Réplication automatique : Turso réplique automatiquement votre base dans les régions de votre groupe. Le groupe default utilise la région la plus proche de vous. Ajoutez des réplicas avec turso db replicate bookmarks-db [region].

Récupérer les credentials

Obtenez votre URL de connexion et votre token :

turso db show bookmarks-db --url
turso db tokens create bookmarks-db

Créez un fichier .env.local à la racine de votre projet :

TURSO_DATABASE_URL=libsql://bookmarks-db-[votre-org].turso.io
TURSO_AUTH_TOKEN=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...

Sécurité : Ne commitez jamais vos tokens Turso dans Git. Ajoutez .env.local à votre .gitignore (Next.js le fait par défaut).

Étape 3 : Installer les dépendances

Installez Drizzle ORM avec le driver LibSQL :

npm install drizzle-orm @libsql/client
npm install -D drizzle-kit

Voici ce que fait chaque package :

PackageRôle
drizzle-ormORM TypeScript type-safe
@libsql/clientDriver LibSQL pour Turso
drizzle-kitCLI pour migrations et introspection

Étape 4 : Configurer Drizzle

Configuration du client de base de données

Créez le fichier de connexion 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 });

Configuration de Drizzle Kit

Créez drizzle.config.ts à la racine du projet :

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,
  },
});

Dialect Turso : Depuis Drizzle Kit v0.22+, le dialect turso est supporté nativement. Il gère les spécificités de LibSQL comme les types integer pour les booléens et les text pour les dates.

Étape 5 : Définir le schéma

Créez src/lib/schema.ts avec les tables pour notre application de bookmarks :

import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
import { relations } from "drizzle-orm";
 
// Table des bookmarks
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()),
});
 
// Table des tags
export const tags = sqliteTable("tags", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  name: text("name").notNull().unique(),
  color: text("color").notNull().default("#6366f1"),
});
 
// Table pivot bookmarks <-> tags
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" }),
});
 
// Relations
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],
  }),
}));

Points clés du schéma

  • SQLite types : LibSQL utilise text et integer comme types de base. Les dates sont stockées en text au format ISO 8601.
  • Relations many-to-many : La table bookmark_tags fait le lien entre bookmarks et tags via une table pivot.
  • Cascade delete : Supprimer un bookmark supprime automatiquement ses associations de tags.
  • Valeurs par défaut : $defaultFn génère les timestamps côté application, pas côté base de données.

Étape 6 : Exécuter les migrations

Générez les fichiers de migration SQL à partir de votre schéma :

npx drizzle-kit generate

Cela crée un dossier drizzle/ avec des fichiers SQL numérotés. Appliquez-les à votre base Turso :

npx drizzle-kit migrate

Vérifiez que les tables ont été créées :

turso db shell bookmarks-db ".tables"

Vous devriez voir : bookmarks, tags, bookmark_tags et les tables internes de migration Drizzle.

Migrations en production : Exécutez toujours drizzle-kit generate localement et commitez les fichiers SQL. Ne lancez jamais drizzle-kit push directement en production — utilisez drizzle-kit migrate qui applique les fichiers générés de manière déterministe.

Étape 7 : Créer les API Routes Edge

Le vrai avantage de Turso se manifeste avec les Edge Functions. Créez des API routes qui tournent en edge runtime.

Route GET/POST pour les bookmarks

Créez 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;
 
  // Filtrage en mémoire pour la recherche
  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();
 
  // Associer les tags
  if (tagIds && tagIds.length > 0) {
    await db.insert(bookmarkTags).values(
      tagIds.map((tagId: number) => ({
        bookmarkId: bookmark.id,
        tagId,
      }))
    );
  }
 
  return NextResponse.json(bookmark, { status: 201 });
}

Route PUT/DELETE pour un bookmark spécifique

Créez 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 }
    );
  }
 
  // Mettre à jour les tags
  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 });
}

Route pour les tags

Créez 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 });
}

Étape 8 : Créer le Server Component principal

Créez la page principale dans src/app/page.tsx qui utilise les Server Components pour le rendu initial :

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">Mes Bookmarks</h1>
      <BookmarkList
        initialBookmarks={allBookmarks}
        tags={allTags}
      />
    </main>
  );
}

Server Component + Edge : En ajoutant export const runtime = "edge" à la page, Next.js exécute le Server Component dans un Edge Runtime. La requête Drizzle vers Turso se fait depuis le datacenter edge le plus proche — résultat : un Time to First Byte ultra-rapide.

Étape 9 : Créer le Client Component

Créez src/components/bookmark-list.tsx pour la partie interactive :

"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>
      {/* Barre de recherche */}
      <div className="mb-6 flex gap-4">
        <input
          type="text"
          placeholder="Rechercher..."
          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="">Tous les tags</option>
          {tags.map((tag) => (
            <option key={tag.id} value={tag.name}>
              {tag.name}
            </option>
          ))}
        </select>
      </div>
 
      {/* Liste des bookmarks */}
      <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"
              >
                Supprimer
              </button>
            </div>
          </div>
        ))}
      </div>
 
      {filteredBookmarks.length === 0 && (
        <p className="text-center text-gray-500">
          Aucun bookmark trouvé.
        </p>
      )}
    </div>
  );
}

Étape 10 : Ajouter un script de seed

Pour tester notre application, créons un script de seed. Créez src/lib/seed.ts :

import { db } from "./db";
import { bookmarks, tags, bookmarkTags } from "./schema";
 
async function seed() {
  console.log("Seeding database...");
 
  // Créer des tags
  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();
 
  // Créer des bookmarks
  const [bm1] = await db
    .insert(bookmarks)
    .values({
      title: "Next.js Documentation",
      url: "https://nextjs.org/docs",
      description: "Documentation officielle de Next.js",
    })
    .returning();
 
  const [bm2] = await db
    .insert(bookmarks)
    .values({
      title: "Turso Documentation",
      url: "https://docs.turso.tech",
      description: "Guide complet pour Turso et LibSQL",
    })
    .returning();
 
  const [bm3] = await db
    .insert(bookmarks)
    .values({
      title: "Drizzle ORM",
      url: "https://orm.drizzle.team",
      description: "ORM TypeScript headless pour SQL",
    })
    .returning();
 
  // Associer tags et bookmarks
  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);

Exécutez le seed :

npx tsx src/lib/seed.ts

Étape 11 : Optimiser les performances edge

Connexions persistantes

Le client LibSQL gère automatiquement un pool de connexions HTTP. Pour les Edge Functions (qui sont stateless), chaque invocation crée une nouvelle connexion. Turso optimise cela avec des connexions HTTP/2 multiplexées.

Mise en cache avec Next.js

Utilisez le cache de Next.js pour les données qui changent rarement :

import { unstable_cache } from "next/cache";
 
export const getCachedTags = unstable_cache(
  async () => {
    return db.select().from(tags);
  },
  ["all-tags"],
  {
    revalidate: 3600, // 1 heure
    tags: ["tags"],
  }
);

Embedded Replicas (optionnel)

Pour des latences encore plus basses en développement local, Turso supporte les embedded replicas — une copie SQLite locale synchronisée automatiquement :

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, // Sync toutes les 60 secondes
});

Embedded Replicas et Edge : Les embedded replicas ne fonctionnent pas en Edge Runtime (pas de système de fichiers). Utilisez-les uniquement pour le développement local ou les serveurs Node.js traditionnels.

Étape 12 : Déployer sur Vercel

Configurer les variables environnement

Dans votre dashboard Vercel, ajoutez les variables suivantes :

  • TURSO_DATABASE_URL — URL de votre base Turso
  • TURSO_AUTH_TOKEN — Token généré avec turso db tokens create

Configurer le runtime Edge

Assurez-vous que vos routes API et pages utilisent export const runtime = "edge". Vercel déploiera automatiquement ces fonctions sur son réseau edge mondial.

Déployer

npx vercel deploy --prod

Ou connectez votre repository Git pour des déploiements automatiques à chaque push.

Vérifier les performances

Après déploiement, testez la latence depuis différentes régions avec un outil comme curl :

curl -o /dev/null -s -w "Time: %{time_total}s\n" \
  https://votre-app.vercel.app/api/bookmarks

Vous devriez observer des temps de réponse inférieurs à 50 ms depuis la plupart des régions, dont une grande partie est due au TLS handshake. La requête Turso elle-même prend généralement entre 1 et 8 ms.

Étape 13 : Ajouter la recherche full-text

Turso supporte la recherche full-text via FTS5, une extension SQLite. Ajoutons-la à notre application.

Créer la table FTS

Créez une migration manuelle drizzle/fts-setup.sql :

CREATE VIRTUAL TABLE IF NOT EXISTS bookmarks_fts USING fts5(
  title,
  description,
  content='bookmarks',
  content_rowid='id'
);
 
-- Triggers pour garder FTS synchronisé
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;

Appliquez cette migration manuellement via le shell Turso :

turso db shell bookmarks-db < drizzle/fts-setup.sql

Requête de recherche

Ajoutez une route de recherche 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);
}

Tests et vérification

Pour vérifier que tout fonctionne :

  1. Lancez le serveur de développement :

    npm run dev
  2. Testez les API routes :

    # Lister les bookmarks
    curl http://localhost:3000/api/bookmarks
     
    # Créer un bookmark
    curl -X POST http://localhost:3000/api/bookmarks \
      -H "Content-Type: application/json" \
      -d '{"title":"Test","url":"https://example.com"}'
     
    # Rechercher
    curl "http://localhost:3000/api/search?q=next"
  3. Vérifiez les données dans Turso :

    turso db shell bookmarks-db "SELECT * FROM bookmarks"

Résolution de problèmes courants

Erreur "TURSO_DATABASE_URL is not defined"

Assurez-vous que votre fichier .env.local est bien à la racine du projet et que vous avez redémarré le serveur de développement après sa création.

Erreur "Token expired"

Les tokens Turso expirent. Régénérez-en un avec :

turso db tokens create bookmarks-db --expiration none

Erreur de migration "table already exists"

Si vous exécutez les migrations plusieurs fois, supprimez les tables et relancez :

turso db shell bookmarks-db "DROP TABLE IF EXISTS bookmark_tags; DROP TABLE IF EXISTS bookmarks; DROP TABLE IF EXISTS tags;"
npx drizzle-kit migrate

Edge Runtime "Module not found"

Certains packages Node.js ne sont pas compatibles avec le Edge Runtime. Vérifiez que vous utilisez uniquement @libsql/client (qui supporte HTTP) et non better-sqlite3 ou un autre driver natif.

Prochaines étapes

Vous avez maintenant une application complète avec une base de données edge-ready. Voici quelques idées pour aller plus loin :

  • Ajouter une authentification avec Better Auth ou Auth.js
  • Implémenter un système de favoris avec des optimistic updates
  • Ajouter des Server Actions pour les mutations côté serveur sans API routes
  • Configurer le monitoring avec OpenTelemetry
  • Créer une extension navigateur qui sauvegarde directement les bookmarks

Conclusion

Dans ce tutoriel, nous avons construit une application de bookmarks edge-ready en combinant trois technologies puissantes :

  • Turso (LibSQL) pour une base de données SQLite distribuée avec réplication automatique
  • Drizzle ORM pour des requêtes TypeScript type-safe et des migrations déclaratives
  • Next.js 15 Edge Runtime pour exécuter le code au plus proche des utilisateurs

Cette stack est idéale pour les applications qui nécessitent des temps de réponse ultra-rapides à travers le monde. La base de données voyage avec votre code — plus besoin de choisir entre performance et simplicité.

Le code complet de ce tutoriel est disponible comme point de départ pour vos propres projets. La combinaison Turso + Drizzle + Next.js Edge représente une des architectures les plus performantes et ergonomiques pour le développement web moderne en 2026.


Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur Implémenter le RAG sur les PDFs avec File Search dans l'API Responses.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.

Articles connexes

Construire un Agent IA Autonome avec Agentic RAG et Next.js

Apprenez a construire un agent IA qui decide de maniere autonome quand et comment recuperer des informations depuis des bases de donnees vectorielles. Un guide pratique complet avec Vercel AI SDK et Next.js, accompagne d'exemples executables.

30 min read·