FR
Leçon 3.3

Convex : votre base de données réactive

Modéliser et interroger des données dans Convex avec une type safety de bout en bout et des mises à jour en direct, et utiliser les index et les soft deletes comme le font les vrais produits

30 minLe stack applicatif moderne - Auth, données et paiementsDisponible

Ce que tu apprends

  • Ce qu'est une base de données versus une feuille de calcul et pourquoi une base réactive change votre façon de construire
  • Schema, queries, mutations et actions, avec type safety de bout en bout de la base de données à l'UI
  • Les index pour des lectures rapides et soft delete versus hard delete avec un vrai pattern de restauration

Vue d'ensemble

Vos utilisateurs peuvent se connecter. Maintenant ils ont besoin de données qui persistent : leur profil, leurs projets, leur travail sauvegardé. C'est la couche base de données, et Convex convient particulièrement bien aux builders auxquels ce cours s'adresse, parce qu'il fait quelque chose que la plupart des bases de données ne font pas - il est réactif. Quand les données changent, chaque partie de votre UI qui dépend de ces données se met à jour d'elle-même, sans code de rafraîchissement manuel. En prime, vos types circulent de bout en bout, de la base de données à vos composants React, si bien qu'une catégorie entière de bugs ne peut simplement pas se produire. Cette leçon enseigne le modèle : ce qu'est une base de données, les quatre types de fonction que vous écrivez, les index et les patterns de suppression qui séparent un jouet d'un vrai produit.

Ce que vous allez apprendre

Vous allez apprendre ce qu'est vraiment une base de données et pourquoi elle bat une feuille de calcul pour une app, ce que la réactivité vous apporte, comment définir un schema et écrire des queries, mutations et actions, comment la type safety de bout en bout empêche les bugs avant qu'ils n'arrivent, quand et pourquoi ajouter un index et la différence entre hard et soft deletes avec un pattern de restauration que vous pouvez copier.

Prérequis

Une app fonctionnelle avec l'auth Clerk de la leçon précédente, car vous voulez lier des données aux utilisateurs connectés. La leçon d'architecture, pour vous souvenir que la base de données est la troisième couche. Et la page de fondamentaux sur ce qu'est une base de données, si l'idée d'un schema ou d'une table est toute neuve. Une familiarité avec du TypeScript simple aide, car dans Convex votre logique de base de données n'est que des fonctions TypeScript.

Le problème

Les débutants saisissent ce qu'ils connaissent : une feuille de calcul, un fichier CSV ou un tas de JSON sauvegardé sur le disque. Cela marche, jusqu'à ce que deux utilisateurs touchent les mêmes données, ou que vous deviez trouver un enregistrement parmi dix mille rapidement, ou que vous changiez la forme de vos données et que tout casse en silence. Alors ils branchent une base de données traditionnelle sur le côté et passent des jours en glue code : récupérer au chargement, re-récupérer après chaque changement, gérer les états de chargement, garder l'UI synchronisée et traquer les bugs de données périmées où l'écran montre de vieux chiffres. L'essentiel de cette glue est du pur gaspillage. Une base de données réactive et typée efface la glue et la classe de bugs avec.

Une base de données n'est pas une feuille de calcul

Une feuille Excel ou un CSV convient pour une liste que vous lisez à l'œil. Une base de données est bâtie pour un autre job : de nombreux utilisateurs qui lisent et écrivent simultanément, trouver des enregistrements précis vite, même parmi des millions, imposer la forme de vos données pour qu'aucune mauvaise ligne ne se glisse, et ne jamais perdre ni corrompre de données quand deux choses arrivent en même temps. Une feuille de calcul n'a aucune idée de qui d'autre l'édite, et aucun moyen de garantir qu'une colonne « prix » est toujours un nombre. Une base de données peut faire les deux. L'upgrade mental est celui-ci : une feuille de calcul est un document que vous regardez ; une base de données est un service à qui votre app parle et qui garantit que les données restent correctes et cohérentes, peu importe le nombre d'utilisateurs ou la charge. Dès l'instant où vos données sont partagées entre utilisateurs ou doivent être interrogées vite, vous avez dépassé la feuille de calcul.

  • Feuille de calcul/CSV : un éditeur à la fois, aucune garantie sur la forme des données, lente à fouiller à grande échelle, facile à corrompre.
  • Base de données : de nombreux utilisateurs simultanés, forme des données imposée (le schema), lookups rapides via index, sûre sous charge.
  • Convex ajoute la réactivité par-dessus : les changements sont poussés automatiquement à chaque client connecté.

Pourquoi la réactivité compte

Dans un setup normal, vous écrivez beaucoup de code pour garder l'écran synchronisé avec la base de données : récupérer au chargement de la page, re-récupérer après avoir changé quelque chose, poller pour les mises à jour d'autres utilisateurs, gérer à la main les états de chargement et d'erreur. Chacun est une occasion de bug où l'UI montre des données périmées. Convex inverse cela. Une query est un abonnement en direct : vous l'écrivez une fois, et chaque fois que les données qu'elle lit changent - que vous les ayez changées ou un autre utilisateur - Convex pousse le nouveau résultat à votre composant et il se re-rend. Vous effacez la logique de re-fetch, le polling et toute la classe de bugs de données périmées. Pour un produit collaboratif ou temps réel (un dashboard, un chat, une liste partagée), c'est transformateur, et même pour une app simple, cela retire un tas de câblage fastidieux et sujet aux erreurs.

Schema, queries, mutations et actions

Convex vous donne quatre blocs de construction. Le schema déclare la forme de vos données - vos tables et leurs champs et types - pour que la base de données puisse l'imposer. Les queries lisent les données et sont réactives. Les mutations changent les données (insert, update, delete) et tournent dans une transaction, donc elles réussissent entièrement ou échouent entièrement. Les actions servent à parler au monde extérieur (appeler une API externe, envoyer un e-mail), là où vous devez faire quelque chose qui n'est pas une pure lecture ou écriture de base de données. Voici un vrai schema plus une query et une mutation, les gestes quotidiens que vous écrirez sans cesse.

// convex/schema.ts - la forme de vos données, imposée par la base de données
import { defineSchema, defineTable } from 'convex/server'
import { v } from 'convex/values'

export default defineSchema({
  projects: defineTable({
    ownerId: v.string(), // l'ID utilisateur Clerk propriétaire de ce projet
    name: v.string(),
    archivedAt: v.optional(v.number()), // null/absent = actif ; un timestamp = soft-deleted
  })
    // Index pour que 'trouver les projets de cet utilisateur' soit un lookup rapide, pas un full scan
    .index('by_owner', ['ownerId']),
})
Un schema avec une table typée et un index. archivedAt est le champ qui alimente le soft delete.
// convex/projects.ts - une query (lecture, réactive) et une mutation (écriture, transactionnelle)
import { query, mutation } from './_generated/server'
import { v } from 'convex/values'

// Réactif : chaque client abonné se re-rend quand les données changent.
export const listActive = query({
  args: { ownerId: v.string() },
  handler: async (ctx, { ownerId }) => {
    const rows = await ctx.db
      .query('projects')
      .withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
      .collect()
    // Les lignes soft-deleted ont archivedAt posé - masquez-les de la liste active.
    return rows.filter((row) => row.archivedAt === undefined)
  },
})

export const create = mutation({
  args: { ownerId: v.string(), name: v.string() },
  handler: async (ctx, { ownerId, name }) => {
    return await ctx.db.insert('projects', { ownerId, name })
  },
})
Les queries lisent et sont en direct ; les mutations écrivent dans une transaction. Les deux sont du simple TypeScript.

Type safety de bout en bout

C'est le super-pouvoir silencieux. Parce que votre schema est du TypeScript et que Convex génère des types à partir de lui, le type de vos données circule tout du long, de la base de données à vos composants React. Si vous renommez un champ dans le schema, chaque query, mutation et composant qui utilisait l'ancien nom devient rouge dans votre éditeur, avant même que vous ne lanciez l'app. Vous ne pouvez pas lire par accident un champ qui n'existe pas, passer le mauvais argument à une mutation ou supposer qu'une string est un nombre. Des classes entières de bugs « ça a crashé en production parce que les données n'avaient pas la forme que je supposais » deviennent impossibles, parce que votre éditeur et le typechecker les attrapent pendant que vous tapez. Pour quelqu'un qui construit avec un agent, c'est énorme : l'agent reçoit les mêmes signaux de type et écrit du code correct bien plus souvent, et votre quality gate de typecheck attrape le reste avant qu'il ne shippe.

Index : rendre les lectures rapides

Quand vous demandez à la base de données « donne-moi tous les projets qui appartiennent à cet utilisateur », elle a deux façons de répondre. Sans index, elle scanne chaque ligne et vérifie chacune - bien pour dix lignes, douloureusement lent pour un million. Avec un index sur ownerId, elle saute directement aux lignes correspondantes, comme utiliser l'index à la fin d'un livre au lieu de lire chaque page. La règle est simple : chaque champ sur lequel vous filtrez ou triez régulièrement mérite un index. Vous l'avez vu dans le schema ci-dessus - l'index by_owner est ce qui rend listActive rapide, peu importe combien de projets existent au total. Ajouter des index tôt ne coûte presque rien ; découvrir que vous en aviez besoin quand votre app se met à ramper sous de vraies données coûte une session de debugging stressante.

  • Pas d'index : la base de données vérifie chaque ligne (un full scan). Bien pour de toutes petites tables, lent à grande échelle.
  • Avec un index : elle saute directement aux lignes correspondantes. Rapide même avec des millions de lignes.
  • Indexez les champs sur lesquels vous filtrez ou triez souvent - ownerId, status, createdAt sont courants.
  • Définissez l'index dans le schema ; utilisez-le dans les queries avec .withIndex(...).

Soft delete versus hard delete, avec restauration

Un hard delete retire une ligne définitivement - elle est partie, et toute chance de la récupérer aussi. Un soft delete garde la ligne mais la marque comme supprimée, souvent avec un timestamp comme le champ archivedAt du schema ci-dessus. Les vrais produits préfèrent presque toujours les soft deletes pour les données utilisateur, car les utilisateurs suppriment sans cesse des choses par accident, et « désolé, c'est parti pour toujours » est une expérience horrible et parfois un problème de conformité. Avec un soft delete, vous pouvez offrir une corbeille ou un undo, restaurer les données et garder l'historique et les audit trails intacts. Vous ne faites un hard delete que plus tard, selon un calendrier, pour des données soft-deleted depuis assez longtemps et que vous avez le droit légal de supprimer. Voici le pattern : supprimer pose le timestamp, restaurer le vide, et un job de fond peut vraiment hard-deleter les lignes très anciennes.

// Soft delete : marquez-le, ne le détruisez pas. Restaurer devient alors trivial.
export const softDelete = mutation({
  args: { id: v.id('projects') },
  handler: async (ctx, { id }) => {
    await ctx.db.patch(id, { archivedAt: Date.now() })
  },
})

export const restore = mutation({
  args: { id: v.id('projects') },
  handler: async (ctx, { id }) => {
    await ctx.db.patch(id, { archivedAt: undefined })
  },
})

// Hard delete : seulement pour des données soft-deleted depuis longtemps, souvent exécuté par un job planifié.
export const purge = mutation({
  args: { id: v.id('projects') },
  handler: async (ctx, { id }) => {
    await ctx.db.delete(id)
  },
})
Le soft delete pose un flag et reste restaurable ; le hard delete est définitif et réservé aux données anciennes et supprimables.

Erreurs fréquentes

Les courantes : écrire du code de re-fetch et de polling manuel quand les queries Convex sont déjà réactives, si bien que vous luttez contre l'outil ; oublier les index et regarder l'app ramper dès que de vraies données arrivent ; hard-deleter des données utilisateur sans restauration puis recevoir le ticket de support que vous ne pouvez pas résoudre ; essayer d'appeler une API externe dans une query ou mutation au lieu d'une action ; et assouplir votre schema (tout optionnel, tout une string) jusqu'à ce que la type safety qui vous protégeait s'évapore. Appuyez-vous sur le schema et la réactivité, au lieu de travailler autour d'eux.

ROI business

Une base de données réactive et typée est à la fois multiplicateur de productivité et multiplicateur de qualité. Vous écrivez radicalement moins de câblage, donc les features shippent plus vite. Les types de bout en bout attrapent les bugs avant qu'ils n'atteignent un client, donc vous shippez avec moins d'incendies. Les index gardent l'app rapide à mesure que vous grandissez, ce qui protège à la fois la conversion et votre réputation. Et le pattern soft delete transforme une catégorie d'incidents de support catastrophiques - « j'ai supprimé tout par accident » - en une restauration en un clic. Pour une fondatrice, le temps gagné sur le câblage et les bugs qui n'arrivent jamais valent bien plus que le coût modéré du service.

Checklist

Vous êtes prêt à avancer quand chacun de ces points est vrai dans une vraie app que vous avez bâtie, pas seulement compris en théorie.

  • Vous savez expliquer pourquoi une base de données bat une feuille de calcul dès que les données sont partagées ou volumineuses.
  • Vous avez un schema avec au moins une table, un index et des fonctions query et mutation fonctionnelles.
  • Vous savez voir les types circuler du schema vers vos composants et attraper une erreur de renommage dans l'éditeur.
  • Vous avez implémenté un soft delete avec une restauration fonctionnelle et savez quand un hard delete est approprié.

Ressources

Les docs Convex sont excellentes et à jour - gardez en favoris les pages sur le schema, les queries, les mutations et les index. La page de fondamentaux sur ce qu'est une base de données ancre les concepts si l'un était bancal. Vous avez maintenant l'auth et les données ; la prochaine leçon sécurise les secrets qui relient chaque service que vous avez ajouté, y compris comment chiffrer les clés API que vos utilisateurs vous confient.

Votre mission

Ajoutez une table Convex pour quelque chose dont votre app a besoin (une liste d'items, de projets ou de notes, liée à l'utilisateur connecté), avec un index sur le propriétaire. Écrivez une query réactive et une mutation de création, puis implémentez soft delete et restore. Supprimez un item, confirmez qu'il disparaît de la liste, puis restaurez-le et regardez-le réapparaître sans rafraîchissement de page. Cette seule exercice prouve que vous comprenez la réactivité, les index et les suppressions restaurables tous à la fois.

Prochaine leçon

Votre app a maintenant des utilisateurs et des données, ce qui veut dire qu'elle a un tas grandissant de clés secrètes qui les relient. La prochaine leçon est la version disciplinée de la gestion des secrets : fichiers env, .gitignore, quoi faire si vous avez déjà poussé un secret, deploy keys Convex et chiffrer les clés API des utilisateurs au lieu de les stocker en clair.

Commentaires

Chargement des commentaires.

Poster un commentaire
CommentairesSuivant
Étape suivante

Prêt à mettre l’IA au service d’un véritable workflow ?

Commencez par le cours de base, conservez vos progrès localement et synchronisez tout sur votre compte gratuit quand vous le souhaitez.