28 janvier 2026

10 Fonctionnalités Cachées de Convex que Personne n'Utilise

10 Fonctionnalités Cachées de Convex que Personne n'Utilise

Convex, c'est le backend qui a révolutionné ma façon de coder. Mais après des mois à creuser la doc, j'ai découvert des pépites cachées que 90% des développeurs n'exploitent pas.

En tant que Product Engineer, je vais vous révéler les 10 features qui changent tout.

1. Scheduled Functions : Le Cron Killer

Oubliez les cron jobs à configurer sur un serveur. Convex permet de planifier des fonctions directement depuis votre code.

typescript
1// Planifier une fonction dans 5 minutes
2await ctx.scheduler.runAfter(5 * 60 * 1000, internal.emails.sendReminder, {
3 userId: args.userId,
4});
5
6// Planifier à une date précise
7await ctx.scheduler.runAt(new Date("2026-02-01T09:00:00Z"), internal.reports.generate, {
8 reportId: args.reportId,
9});

La magie ? Si votre mutation réussit, la fonction est garantie de s'exécuter. Si elle échoue, rien n'est planifié. Atomicité parfaite.

  • Jusqu'à 1000 fonctions planifiées simultanément
  • Délai de quelques millisecondes à plusieurs années
  • Visible dans le dashboard Convex

2. Cron Jobs Natifs

Pour les tâches récurrentes, Convex a son propre système de crons. Créez un fichier convex/crons.ts :

typescript
1import { cronJobs } from "convex/server";
2import { internal } from "./_generated/api";
3
4const crons = cronJobs();
5
6// Toutes les heures
7crons.interval("sync-data", { hours: 1 }, internal.sync.pullFromAPI);
8
9// Chaque jour à 9h UTC
10crons.daily("daily-report", { hourUTC: 9, minuteUTC: 0 }, internal.reports.daily);
11
12// Syntaxe cron classique
13crons.cron("cleanup", "0 0 * * 0", internal.cleanup.weeklyPurge);
14
15export default crons;

Avantage vs cron traditionnel : Pas de serveur à maintenir, logs intégrés, et vos jobs sont versionnés avec votre code.

3. Vector Search Intégré

Pas besoin de Pinecone ou Qdrant pour un MVP. Convex a son propre moteur de recherche vectorielle.

typescript
1// schema.ts - Définir l'index vectoriel
2export default defineSchema({
3 documents: defineTable({
4 content: v.string(),
5 embedding: v.array(v.float64()),
6 }).vectorIndex("by_embedding", {
7 vectorField: "embedding",
8 dimensions: 1536, // OpenAI embeddings
9 filterFields: ["category"],
10 }),
11});
typescript
1// Rechercher les documents similaires
2const results = await ctx.vectorSearch("documents", "by_embedding", {
3 vector: queryEmbedding,
4 limit: 10,
5 filter: (q) => q.eq("category", "tech"),
6});
flowchart LR
    A[User Query] --> B[Generate Embedding]
    B --> C[Vector Search]
    C --> D[Top K Documents]
    D --> E[LLM Context]
    E --> F[AI Response]
    
    style A fill:#FC5757,color:#fff
    style C fill:#FC5757,color:#fff
    style F fill:#1a1d24,stroke:#FC5757

Le plus ? La recherche est immédiatement cohérente. Contrairement aux bases vectorielles externes, pas de délai de synchronisation.

4. File Storage Sans Config

Upload, stockage, et serving de fichiers en 3 lignes de code. Pas de S3 à configurer.

typescript
1// Générer une URL d'upload (valide 1h)
2const uploadUrl = await ctx.storage.generateUploadUrl();
3
4// Récupérer l'URL publique d'un fichier
5const fileUrl = await ctx.storage.getUrl(storageId);
6
7// Supprimer un fichier
8await ctx.storage.delete(storageId);

Features cachées :

  • Support de tous les types de fichiers
  • URLs signées avec expiration
  • Streaming depuis les HTTP Actions
  • Métadonnées accessibles

5. HTTP Actions : Votre API REST

Convex peut exposer des endpoints HTTP pour recevoir des webhooks ou créer une API REST.

typescript
1// convex/http.ts
2import { httpRouter } from "convex/server";
3import { httpAction } from "./_generated/server";
4
5const http = httpRouter();
6
7http.route({
8 path: "/webhooks/stripe",
9 method: "POST",
10 handler: httpAction(async (ctx, request) => {
11 const body = await request.json();
12 const signature = request.headers.get("stripe-signature");
13
14 // Valider et traiter le webhook
15 await ctx.runMutation(internal.payments.processWebhook, {
16 event: body,
17 });
18
19 return new Response("OK", { status: 200 });
20 }),
21});
22
23export default http;

Vos endpoints sont accessibles sur https://votre-projet.convex.site/webhooks/stripe.

Use cases :

  • Webhooks (Stripe, GitHub, Discord)
  • API publique pour apps tierces
  • Intégration avec Zapier/n8n

6. Optimistic Updates

Rendez votre UI instantanément réactive avant même que le serveur réponde.

typescript
1const addTodo = useMutation(api.todos.add).withOptimisticUpdate(
2 (localStore, args) => {
3 const currentTodos = localStore.getQuery(api.todos.list);
4 if (currentTodos !== undefined) {
5 localStore.setQuery(api.todos.list, {}, [
6 ...currentTodos,
7 { _id: "temp", text: args.text, completed: false },
8 ]);
9 }
10 }
11);

Comment ça marche ?

sequenceDiagram
    participant UI
    participant LocalStore
    participant Convex
    
    UI->>LocalStore: Optimistic Update
    LocalStore->>UI: UI Updated (instant)
    UI->>Convex: Mutation Request
    Convex->>Convex: Process Mutation
    Convex->>LocalStore: Real Data
    LocalStore->>UI: Reconcile (auto)

Si le serveur renvoie des données différentes, Convex rollback automatiquement l'update optimiste.

7. Internal Functions : L'Encapsulation Parfaite

Les internalQuery, internalMutation, et internalAction sont invisibles côté client. Parfait pour la logique sensible.

typescript
1// Accessible uniquement depuis le backend
2export const chargeCustomer = internalMutation({
3 args: { userId: v.id("users"), amount: v.number() },
4 handler: async (ctx, args) => {
5 // Logique de facturation sensible
6 const user = await ctx.db.get(args.userId);
7 await stripe.charges.create({ amount: args.amount, customer: user.stripeId });
8 await ctx.db.patch(args.userId, { lastCharged: Date.now() });
9 },
10});
11
12// Appel depuis une autre fonction
13await ctx.runMutation(internal.billing.chargeCustomer, { userId, amount: 1999 });

Sécurité : Ces fonctions n'apparaissent pas dans l'objet api exposé au client. Impossible de les appeler depuis le frontend.

8. Pagination Réactive

La pagination Convex est fully reactive. Quand un document change, votre page se met à jour automatiquement.

typescript
1// Backend
2export const listMessages = query({
3 args: { paginationOpts: paginationOptsValidator },
4 handler: async (ctx, args) => {
5 return await ctx.db
6 .query("messages")
7 .order("desc")
8 .paginate(args.paginationOpts);
9 },
10});
11
12// Frontend - Infinite scroll automatique
13const { results, status, loadMore } = usePaginatedQuery(
14 api.messages.list,
15 {},
16 { initialNumItems: 20 }
17);

Le trick ? Combinez avec l'Intersection Observer pour un infinite scroll parfait :

typescript
1useEffect(() => {
2 const observer = new IntersectionObserver(
3 ([entry]) => entry.isIntersecting && status === "CanLoadMore" && loadMore(20)
4 );
5 observer.observe(sentinelRef.current);
6 return () => observer.disconnect();
7}, [status, loadMore]);

9. Components : Modules Réutilisables

Convex permet de créer des composants backend packagés comme des modules NPM.

typescript
1// Installation
2npm install @convex-dev/aggregate
3
4// Utilisation
5import { Aggregate } from "@convex-dev/aggregate";
6
7const aggregate = new Aggregate<number>(components.aggregate, {
8 sortKey: (doc) => doc.createdAt,
9 sumValue: (doc) => doc.amount,
10});
11
12// Obtenir des stats en temps réel
13const stats = await aggregate.sum(ctx, { prefix: ["sales", "2026"] });

Components officiels disponibles :

  • @convex-dev/aggregate - Agrégations temps réel
  • @convex-dev/auth - Authentification complète
  • @convex-dev/rate-limiter - Rate limiting
  • @convex-dev/crons - Crons avancés

10. Validation avec Zod

Combinez la puissance de Zod avec Convex pour une validation avancée :

typescript
1import { zCustomMutation } from "convex-helpers/server/zod";
2import { z } from "zod";
3
4const mutation = zCustomMutation(baseMutation);
5
6export const createUser = mutation({
7 args: {
8 email: z.string().email(),
9 age: z.number().min(18).max(120),
10 preferences: z.object({
11 theme: z.enum(["light", "dark"]),
12 notifications: z.boolean(),
13 }),
14 },
15 handler: async (ctx, args) => {
16 // args est parfaitement typé et validé
17 return await ctx.db.insert("users", args);
18 },
19});

Avantages vs validators natifs :

  • Validations complexes (regex, min/max, enums)
  • Messages d'erreur personnalisés
  • Transformations de données
  • Réutilisation des schémas

Récapitulatif

| Feature | Use Case | Impact | |---------|----------|--------| | Scheduled Functions | Emails, rappels, jobs différés | ⭐⭐⭐⭐⭐ | | Vector Search | RAG, semantic search | ⭐⭐⭐⭐⭐ | | HTTP Actions | Webhooks, API REST | ⭐⭐⭐⭐ | | Optimistic Updates | UX instantanée | ⭐⭐⭐⭐ | | File Storage | Upload/download | ⭐⭐⭐⭐ | | Components | Code réutilisable | ⭐⭐⭐ |


Leçon apprise : Convex n'est pas juste une "base de données réactive". C'est un backend complet qui remplace Supabase, Firebase, et même certains services AWS. Creusez la doc, les pépites sont partout.