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

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) oucurl -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-bookmarksSé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 loginCela ouvrira votre navigateur pour authentification. Une fois connecté, créez votre base de données :
turso db create bookmarks-db --group defaultRé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-dbCré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-kitVoici ce que fait chaque package :
| Package | Rôle |
|---|---|
drizzle-orm | ORM TypeScript type-safe |
@libsql/client | Driver LibSQL pour Turso |
drizzle-kit | CLI 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
textetintegercomme types de base. Les dates sont stockées entextau format ISO 8601. - Relations many-to-many : La table
bookmark_tagsfait 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 :
$defaultFngé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 generateCela crée un dossier drizzle/ avec des fichiers SQL numérotés. Appliquez-les à votre base Turso :
npx drizzle-kit migrateVé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 TursoTURSO_AUTH_TOKEN— Token généré avecturso 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 --prodOu 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/bookmarksVous 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.sqlRequê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 :
-
Lancez le serveur de développement :
npm run dev -
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" -
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 noneErreur 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 migrateEdge 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.
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 une Application Full-Stack avec Drizzle ORM et Next.js 15 : Base de Donnees Type-Safe du Zero a la Production
Apprenez a construire une application full-stack type-safe avec Drizzle ORM et Next.js 15. Ce tutoriel pratique couvre la conception de schemas, les migrations, les Server Actions, les operations CRUD et le deploiement avec PostgreSQL.

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.

Construire un moteur de recherche sémantique avec Next.js 15, OpenAI et Pinecone
Apprenez à construire un moteur de recherche sémantique prêt pour la production avec Next.js 15, OpenAI Embeddings et la base de données vectorielle Pinecone. Ce tutoriel complet couvre la configuration, l'indexation, les requêtes et le déploiement.