#Drizzle content loader/layer

68 messages · Page 1 of 1 (latest)

stark lark
#

Salut, j’ai crée mon site avec content collections et c’est cool. Maintenant je veux le même site mais en passant par postgresql via drizzle. J’ai déjà tout config et tout fonctionne. Y’a pas un truc du genre content loader ou layer dans le content config avec glob pour ne pas avoir à modifier mes pages ? Juste le fichier content config ou autres?

crimson ermineBOT
#

Hi @stark lark. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Salut ! Oui, Astro te permet d’utiliser un “content loader” personnalisé pour brancher tes collections de contenu sur une source externe comme PostgreSQL via Drizzle, sans avoir à modifier tes pages existantes. L’idée est de remplacer le loader glob habituel dans ton fichier src/content.config.ts par un loader custom qui va chercher tes données dans ta base PostgreSQL.

Voici comment tu pourrais procéder :

  1. Créer un loader custom
    Tu peux définir un loader inline ou comme objet, qui va interroger ta base via Drizzle et retourner un tableau d’objets avec un champ id unique pour chaque entrée.

    Exemple minimal :

    import { defineCollection, z } from 'astro:content';
    import { db, posts } from './drizzle'; // ton setup drizzle
    
    const blog = defineCollection({
      loader: async () => {
        const data = await db.select().from(posts);
        return data.map((row) => ({
          id: row.id, // ou slug, selon ta structure
          ...row,
        }));
      },
      schema: z.object({
        title: z.string(),
        // ...autres champs
      }),
    });
    
    export const collections = { blog };
    
  2. Pas besoin de modifier tes pages
    Tant que tu gardes le même nom de collection (blog dans l’exemple) et que tu exposes les mêmes champs dans le schéma, tes pages qui utilisent getCollection('blog') ou getEntry('blog', ...) continueront de fonctionner sans changement : tu n’as qu’à modifier le loader dans content.config.ts ! Voir la doc sur les loaders custom.

  3. Respecter la structure attendue
    Assure-toi que chaque entrée retournée par ton loader a bien un champ id unique, et que les champs correspondent à ceux attendus par ton schéma Zod.

#

En résumé : tu peux tout à fait brancher Drizzle/PostgreSQL à Astro via un loader custom dans content.config.ts, sans toucher à tes pages, exactement comme tu le ferais avec glob pour des fichiers locaux. C’est l’un des gros avantages du Content Layer API d’Astro ! Plus d’infos ici et ici pour les types de loader.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine oui detaille et developpe, ne reste ni simple ni minimaliste !!!! Focus sur le custom loader stp

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Bien sûr, voici une explication détaillée et approfondie sur la création d’un custom loader Astro pour brancher tes content collections sur une base PostgreSQL via Drizzle, tout en gardant la compatibilité avec l’API de content collections d’Astro (donc sans toucher à tes pages).


1. Principe général des custom loaders dans Astro

Astro permet de définir un loader personnalisé dans le fichier src/content.config.ts (ou .js/.mjs). Ce loader peut être :

  • Inline : une fonction async qui retourne un tableau d’objets { id, ... } (simple, mais peu flexible).
  • Objet loader : un objet avec une méthode load(context) pour un contrôle avancé (recommandé pour des cas complexes ou pour gérer l’incrémental, la validation, etc.)<br>
    Voir la doc sur les types de loader.

2. Structure d’un custom loader objet

Un objet loader doit avoir :

  • name : un nom unique pour le loader.
  • load(context) : une fonction async qui reçoit un LoaderContext et qui va peupler le store avec les entrées.
  • (optionnel) schema : un schéma Zod pour la validation et la génération des types TypeScript.

Exemple de structure :

import type { Loader, LoaderContext } from 'astro/loaders';
import { z } from 'astro:content';

export function drizzleLoader(options): Loader {
  return {
    name: 'drizzle-loader',
    load: async (context: LoaderContext) => {
      // Ici tu vas interroger ta base PostgreSQL via Drizzle
      // et ajouter les entrées au store Astro
    },
    schema: z.object({
      // ... ton schéma Zod
    }),
  };
}

Voir la doc sur l’API objet loader


#

3. Utilisation du LoaderContext

Le context passé à load() te donne accès à :

  • store : pour ajouter, mettre à jour ou supprimer des entrées.
  • meta : pour stocker des infos de synchronisation (tokens, timestamps, etc.).
  • logger : pour loguer proprement.
  • config : la config Astro complète.
  • parseData : pour valider et parser tes données selon le schéma Zod.

Exemple d’utilisation :

load: async ({ store, logger, parseData }) => {
  logger.info('Chargement des articles depuis PostgreSQL via Drizzle');
  const articles = await db.select().from(posts); // Drizzle

  for (const article of articles) {
    const data = await parseData({
      id: article.id,
      data: article,
    });
    store.set({
      id: article.id,
      data,
    });
  }
}

Voir la doc sur LoaderContext


4. Définir le schéma Zod

Le schéma Zod est fortement recommandé pour :

  • Valider les données à l’import.
  • Générer automatiquement les types TypeScript pour l’autocomplétion et la sécurité.

Exemple :

schema: z.object({
  id: z.string(),
  title: z.string(),
  content: z.string(),
  author: z.string(),
  publishedAt: z.coerce.date(),
})

Voir la doc sur les schémas


5. Intégration dans content.config.ts

Tu importes ton loader et tu le passes à defineCollection :

import { defineCollection } from 'astro:content';
import { drizzleLoader } from './drizzle-loader';

const blog = defineCollection({
  loader: drizzleLoader({ /* options si besoin */ }),
  // Le schéma peut être dans le loader ou ici
});
#

export const collections = { blog };

[Voir la doc sur la config de collection](<https://docs.astro.build/en/guides/content-collections/#defining-collections>)

---

## 6. Résultat côté pages Astro

**Aucune modification à faire dans tes pages** :  
Tu continues d’utiliser `getCollection('blog')`, `getEntry('blog', ...)`, etc.  
Astro va utiliser ton loader custom pour remplir la collection, comme il le ferait avec un loader `glob` ou `file`.

---

## 7. Exemple complet (pseudo-code)

```ts
// drizzle-loader.ts
import type { Loader, LoaderContext } from 'astro/loaders';
import { z } from 'astro:content';
import { db, posts } from './drizzle';

export function drizzleLoader(): Loader {
  return {
    name: 'drizzle-loader',
    load: async ({ store, logger, parseData }) => {
      logger.info('Chargement des posts depuis PostgreSQL');
      const rows = await db.select().from(posts);
      for (const row of rows) {
        const data = await parseData({ id: row.id, data: row });
        store.set({ id: row.id, data });
      }
    },
    schema: z.object({
      id: z.string(),
      title: z.string(),
      content: z.string(),
      publishedAt: z.coerce.date(),
    }),
  };
}

// src/content.config.ts
import { defineCollection } from 'astro:content';
import { drizzleLoader } from './drizzle-loader';

const blog = defineCollection({
  loader: drizzleLoader(),
});

export const collections = { blog };

8. Points avancés

  • Incremental update : tu peux choisir de ne pas vider le store à chaque chargement, mais d’ajouter/mettre à jour/supprimer seulement ce qui a changé.
  • Meta store : stocke des infos de sync (ex : last sync date) pour ne charger que les nouveaux/MAJ.
  • Logger : pour des logs propres et filtrables.
  • parseData : pour garantir la validation Zod avant insertion.

9. Références utiles

#

En résumé :
Tu peux totalement brancher Drizzle/PostgreSQL à Astro via un custom loader objet, en gardant la même API côté pages. Le cœur du travail est dans le loader, qui fait le pont entre ta base et le content layer Astro, avec validation, typage, logs, etc.
Aucune modification à faire dans tes pages, tout se passe dans le loader et la config de collection !

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine Je suis obligé d’expliciter chaque entrée ? J’ai déjà un fichier schema c’est pas censé être déjà typé?

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Non, tu n’es pas obligé d’expliciter chaque entrée dans ton loader : c’est justement le rôle du schéma Zod que tu déclares dans ta collection. Le schéma sert à valider et typer automatiquement chaque entrée que tu ajoutes à la collection, que ce soit via un loader custom ou un loader glob. Tant que tes objets retournés par le loader respectent la forme attendue par le schéma (mêmes clés, bons types), tu n’as pas besoin de réécrire ou de typer chaque champ manuellement dans le code du loader.

Dans un loader custom, tu dois simplement t’assurer que chaque objet retourné possède au minimum un champ id unique, et que les autres champs correspondent à ceux définis dans le schéma. La validation et le typage sont ensuite assurés automatiquement par Astro grâce à Zod :

Schemas enforce consistent frontmatter or entry data within a collection through Zod validation. A schema guarantees that this data exists in a predictable form when you need to reference or query it. If any file violates its collection schema, Astro will provide a helpful error to let you know.
Schemas also power Astro’s automatic TypeScript typings for your content. When you define a schema for your collection, Astro will automatically generate and apply a TypeScript interface to it. The result is full TypeScript support when you query your collection, including property autocompletion and type-checking.
Defining the collection schema

Donc :

  • Non, tu n’as pas à réécrire chaque champ dans le loader si tu as déjà un schéma Zod.
  • Oui, le schéma suffit pour la validation et le typage automatique de toutes les entrées de ta collection, peu importe leur origine (fichiers, base de données, API, etc.).
#

Il te suffit de retourner des objets qui respectent ce schéma, et Astro s’occupe du reste !

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine Du coup donne moi un exemple avec postcategories, postcategoriestranslations, posts, posttranslations, authors, authortranslations, comments.

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Voici comment tu pourrais structurer un custom loader Astro pour charger plusieurs collections (postcategories, postcategoriestranslations, posts, posttranslations, authors, authortranslations, comments) depuis une base PostgreSQL via Drizzle, tout en profitant de la validation et du typage automatique grâce aux schémas Zod dans Astro.

1. Exemple de schéma Zod pour chaque collection

Dans ton src/content.config.ts, tu définis chaque collection avec son loader et son schéma :

import { defineCollection, z } from 'astro:content';
import { drizzleLoader } from './drizzle-loader';

// Exemple de schémas Zod pour chaque collection
const postCategories = defineCollection({
  loader: drizzleLoader('postcategories'),
  schema: z.object({
    id: z.string(),
    name: z.string(),
  }),
});

const postCategoriesTranslations = defineCollection({
  loader: drizzleLoader('postcategoriestranslations'),
  schema: z.object({
    id: z.string(),
    categoryId: z.string(),
    language: z.string(),
    name: z.string(),
  }),
});

const posts = defineCollection({
  loader: drizzleLoader('posts'),
  schema: z.object({
    id: z.string(),
    categoryId: z.string(),
    authorId: z.string(),
    title: z.string(),
    content: z.string(),
    publishedAt: z.coerce.date(),
  }),
});

const postTranslations = defineCollection({
  loader: drizzleLoader('posttranslations'),
  schema: z.object({
    id: z.string(),
    postId: z.string(),
    language: z.string(),
    title: z.string(),
    content: z.string(),
  }),
});

const authors = defineCollection({
  loader: drizzleLoader('authors'),
  schema: z.object({
    id: z.string(),
    name: z.string(),
    bio: z.string(),
  }),
});
#

const authorTranslations = defineCollection({
loader: drizzleLoader('authortranslations'),
schema: z.object({
id: z.string(),
authorId: z.string(),
language: z.string(),
bio: z.string(),
}),
});

const comments = defineCollection({
loader: drizzleLoader('comments'),
schema: z.object({
id: z.string(),
postId: z.string(),
author: z.string(),
body: z.string(),
publishedAt: z.coerce.date(),
}),
});

export const collections = {
postCategories,
postCategoriesTranslations,
posts,
postTranslations,
authors,
authorTranslations,
comments,
};

[Voir la structure de `defineCollection` et l’utilisation de schémas Zod](<https://docs.astro.build/en/guides/content-collections/#defining-collections>)

---

### 2. Exemple de loader Drizzle générique

Tu peux créer un loader générique qui prend le nom de la table et retourne les entrées :

```ts
// drizzle-loader.ts
import type { Loader, LoaderContext } from 'astro/loaders';
import { db } from './drizzle'; // Ton instance Drizzle

export function drizzleLoader(tableName: string): Loader {
  return {
    name: `drizzle-loader-${tableName}`,
    load: async ({ store, logger, parseData }) => {
      logger.info(`Chargement des données depuis la table ${tableName}`);
      const rows = await db.select().from(db[tableName]);
      for (const row of rows) {
        const data = await parseData({ id: row.id, data: row });
        store.set({ id: row.id, data });
      }
    },
  };
}

Voir la structure d’un custom loader objet et l’utilisation de store.set


3. Points importants

#
  • Chaque collection a son propre schéma Zod pour la validation et le typage automatique.
  • Le loader se contente de retourner les données de la base ; la validation est assurée par le schéma.
  • Aucune modification à faire dans tes pages Astro : tu continues d’utiliser getCollection('posts'), etc.

Si tu veux gérer les relations (ex : categoryId, authorId), tu peux utiliser la fonction reference() dans le schéma pour lier les collections, comme expliqué dans la documentation :
Définir des références entre collections.


Remarque :
Les exemples ci-dessus sont conformes à la documentation officielle Astro sur les custom loaders et l’utilisation de schémas Zod pour la validation et le typage automatique. Si tu as déjà un fichier de schéma, tu peux l’importer et l’utiliser directement dans la propriété schema de chaque collection, sans avoir à réécrire chaque champ dans le loader lui-même.
Voir la doc sur les schémas et loaders

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine du coup le loader il est abstrait? Tu as un exemple complet et intégrale de loader drizzle ?

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Le loader peut effectivement être abstrait : tu peux créer une fonction générique qui prend le nom d’une table (ou tout autre paramètre) et retourne un objet loader compatible avec Astro. Ce pattern est recommandé pour éviter la duplication de code si tu as plusieurs tables à charger de la même façon.

Exemple complet d’un loader Drizzle pour Astro

Voici un exemple basé sur la documentation officielle Astro pour les custom loaders :

// drizzle-loader.ts
import type { Loader, LoaderContext } from 'astro/loaders';
import { db } from './drizzle'; // Ton instance Drizzle, déjà configurée

export function drizzleLoader(table: any): Loader {
  return {
    name: `drizzle-loader-${table._.name}`, // ou autre identifiant unique
    load: async ({ store, logger, parseData }) => {
      logger.info(`Chargement des données depuis la table ${table._.name}`);
      const rows = await db.select().from(table);
      for (const row of rows) {
        const data = await parseData({ id: row.id, data: row });
        store.set({ id: row.id, data });
      }
    },
  };
}

Utilisation dans content.config.ts :

import { defineCollection, z } from 'astro:content';
import { drizzleLoader } from './drizzle-loader';
import { posts, authors, comments } from './drizzle'; // Tes tables Drizzle

const postsCollection = defineCollection({
  loader: drizzleLoader(posts),
  schema: z.object({
    id: z.string(),
    title: z.string(),
    // ...autres champs
  }),
});

const authorsCollection = defineCollection({
  loader: drizzleLoader(authors),
  schema: z.object({
    id: z.string(),
    name: z.string(),
    // ...autres champs
  }),
});

const commentsCollection = defineCollection({
  loader: drizzleLoader(comments),
  schema: z.object({
    id: z.string(),
    postId: z.string(),
    body: z.string(),
    // ...autres champs
  }),
});
#

export const collections = {
posts: postsCollection,
authors: authorsCollection,
comments: commentsCollection,
// ...autres collections
};


**Remarques importantes :**
- Le loader est bien abstrait : il ne dépend que de la table passée en paramètre.
- Tu n’as pas besoin de réécrire la logique pour chaque collection.
- La validation et le typage sont assurés par le schéma Zod de chaque collection, pas par le loader lui-même.

Ce pattern est conforme à la documentation Astro sur les custom loaders et l’utilisation du Content Loader API : tu utilises un objet loader avec une méthode `load` qui peuple le store à partir de ta source de données externe, ici Drizzle/PostgreSQL.  
[Voir la section sur les object loaders et le Content Loader API](<https://docs.astro.build/en/reference/content-loader-reference/#object-loaders>)
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine ça a l’air idéal mais pas parfait, ou le contraire. T’as mieux encore?

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Ta solution avec un custom loader Drizzle abstrait est effectivement la méthode recommandée par Astro pour brancher une base de données externe (comme PostgreSQL via Drizzle) sur le système de content collections, tout en gardant la compatibilité avec l’API Astro (getCollection, etc.) et la validation automatique via Zod. C’est ce que la documentation Astro propose pour charger du contenu distant, et il n’existe pas de méthode “plus native” ou “plus magique” à ce jour : le Content Layer API d’Astro est justement conçu pour permettre ce genre d’intégration flexible, que ce soit avec des fichiers locaux ou des sources distantes via des loaders personnalisésVoir la doc sur les custom loaders.

Les avantages de cette approche :

  • Aucune modification dans tes pages : tu gardes la même API Astro.
  • Validation et typage automatique grâce à Zod.
  • Loader réutilisable et abstrait pour toutes tes tables.
  • Compatible avec la philosophie Astro (peu de JS côté client, typage fort, etc.).

Les limites :

  • Tu dois maintenir le code du loader (mais il reste très simple).
  • Les relations entre tables (joins, etc.) doivent être gérées côté loader ou dans la base, pas automatiquement par Astro.

Il n’existe pas, dans la documentation officielle, de solution plus simple ou plus intégrée pour brancher une base Drizzle/PostgreSQL sur le content layer Astro. Si tu veux une intégration encore plus “magique”, il faudrait attendre une évolution d’Astro ou une intégration tierce spécifique, mais ce n’est pas documenté à ce jour.

#

En résumé :
Ce pattern est le plus abouti et recommandé actuellement par Astro pour ton besoin, et il est déjà très flexible et puissantLoader types et custom loaders.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine J’ai pas compris comment je dois gere les relations du coup? Moi j’ai juste avec drizzle dans mon migrate relies les tables via id c’est tout

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Pour gérer les relations entre tables dans Astro avec un custom loader Drizzle, il faut distinguer deux aspects :

  1. Côté base de données (Drizzle/PostgreSQL)
    Tu fais bien : tu relies tes tables via des colonnes d’ID (authorId, categoryId, etc.) dans tes migrations et ton schéma de base de données. C’est la bonne pratique pour la structure relationnelle.

  2. Côté Astro (content collections)
    Astro ne gère pas automatiquement les relations comme un ORM. Quand tu utilises un custom loader, tu importes les données de chaque table séparément. Les relations (ex : afficher l’auteur d’un post) sont gérées dans tes pages Astro : tu utilises les IDs pour faire des requêtes croisées.

Exemple :

  • Ton loader pour posts importe chaque post avec un champ authorId.
  • Ton loader pour authors importe chaque auteur avec son id.
  • Dans ta page Astro, tu peux faire :
---
// Récupère tous les posts et tous les auteurs
import { getCollection } from 'astro:content';
const posts = await getCollection('posts');
const authors = await getCollection('authors');

// Associe chaque post à son auteur
const postsWithAuthors = posts.map(post => ({
  ...post,
  author: authors.find(a => a.id === post.authorId),
}));
---

Astro ne fait pas de “join” automatique : tu relies les données côté page ou composant, en utilisant les IDs comme tu le ferais avec des fichiers.
Si tu veux, tu peux aussi faire le “join” directement dans ton loader Drizzle (en utilisant .innerJoin() par exemple), et retourner des objets déjà enrichis, mais ce n’est pas obligatoire : c’est à toi de choisir où tu veux faire l’association des donnéesVoir la doc sur les relations dans Astro DB.

#

Résumé :

  • Les relations sont définies dans la base via les IDs (comme tu fais déjà).
  • Astro ne gère pas les relations automatiquement dans les content collections : tu relies les données dans tes pages ou dans le loader si tu veux.
  • Tu peux utiliser les IDs pour faire le lien entre les collections dans Astro, comme tu le ferais avec des fichiers ou des données locales.

Si tu veux des exemples plus précis de “join” avec Drizzle dans un loader, n’hésite pas à demander !

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine ok mais là c’est théorique car dans la vraie vie ok on a l’id de l’auteur mais après on veut sa data !!! Son nom, etc par exemple

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Tu as raison, dans la pratique, avoir juste l’id de l’auteur ne suffit pas : tu veux accéder à ses données (nom, bio, etc.).
Avec Astro content collections et un custom loader, voici comment tu peux procéder :

  1. Chaque collection (posts, authors, etc.) est chargée séparément via son loader, qui importe toutes les données de la table correspondante.

  2. Dans ta page Astro, tu récupères toutes les entrées nécessaires :

import { getCollection } from 'astro:content';

const posts = await getCollection('posts');
const authors = await getCollection('authors');
  1. Pour chaque post, tu relies l’auteur via son id :
const postsWithAuthors = posts.map(post => ({
  ...post,
  author: authors.find(a => a.id === post.authorId),
}));
  1. Tu peux ensuite afficher le nom de l’auteur dans ton template :
<ul>
  {postsWithAuthors.map(post => (
    <li>
      <h2>{post.data.title}</h2>
      <p>Auteur : {post.author?.data.name}</p>
    </li>
  ))}
</ul>

Astro ne fait pas de “join” automatique : tu relies les données côté page ou composant, en utilisant les IDs comme tu le ferais avec des fichiers.
C’est la méthode recommandée dans la documentation Astro pour accéder aux données référencées dans les content collections, que ce soit avec des fichiers ou un custom loader :

Any references defined in your schema must be queried separately after first querying your collection entry.
Accessing referenced data

Si tu utilises la fonction reference() dans ton schéma, tu peux aussi utiliser getEntry() pour récupérer la donnée référencée :

#
const author = await getEntry(post.data.author); // si tu utilises reference('authors')

Defining collection references

En résumé :

  • Tu récupères les données de chaque collection séparément.
  • Tu relies les entrées via leurs IDs dans ton code (page ou composant).
  • Tu accèdes ainsi à toutes les données de l’auteur (ou autre entité liée) pour les afficher dans ton site.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine C’est pas performant de générer toutes les donnes et c’est con !!

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Tu as raison de te poser la question de la performance : charger toutes les données de toutes les collections pour ensuite faire le “join” côté page peut devenir inefficace si tu as beaucoup d’entrées.
La documentation Astro précise que les content collections sont conçues pour être performantes et scalables, mais elles sont surtout optimisées pour des cas où tu as besoin de charger un ensemble de contenus partageant la même structure, et où la quantité de données reste raisonnable pour un site statique ou semi-dynamique. Pour des cas de données très volumineuses ou de relations complexes, Astro recommande d’utiliser d’autres méthodes de récupération de données, comme des requêtes directes ou des APIs, surtout si tu as besoin de données en temps réel ou filtrées dynamiquement :

You are using APIs that need to be updated in real time. Content collections are only updated at build time, so if you need live data, use other methods of importing files or fetching data with on-demand rendering.
When not to create a collection

Si tu veux éviter de charger toutes les données, tu peux :

  • Faire le “join” côté loader (dans Drizzle, avec .innerJoin() par exemple) pour ne charger que ce dont tu as besoin.
  • Utiliser des méthodes de récupération de données plus ciblées (API, requêtes directes) dans tes pages Astro, surtout si tu utilises on-demand rendering.
#

En résumé :
Charger toutes les données et faire le “join” côté page est simple et fonctionne bien pour des sites de taille raisonnable, mais ce n’est pas optimal pour de très gros volumes. Dans ce cas, il vaut mieux faire le “join” côté base de données ou loader, ou utiliser des méthodes de récupération de données plus dynamiques et ciblées.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine bah OUI j’aimerais plutôt préparer mes requetes dans le loader ou content config je sais pas !!! Exemple, les posts d’une seule langue, un singlepost (getentry) récupérés les infos d’un auteur et les commentaires. Etc etc

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Tu as raison : pour des besoins avancés (filtrage, jointures, requêtes ciblées), il est préférable de préparer tes requêtes directement dans le loader custom, plutôt que de charger toutes les données et de faire le tri côté page.
Astro te permet de faire cela dans le loader, car le loader est un code JavaScript/TypeScript où tu peux écrire toute la logique que tu veux avant de remplir le store de la collection.

1. Filtrer dans le loader (ex : posts d’une seule langue)

Dans ton loader, tu peux filtrer les résultats avant de les injecter dans le store :

export function drizzleLoader(table, filterFn) {
  return {
    name: `drizzle-loader-${table._.name}`,
    load: async ({ store, logger, parseData }) => {
      logger.info(`Chargement filtré depuis ${table._.name}`);
      let rows = await db.select().from(table);
      if (filterFn) {
        rows = rows.filter(filterFn);
      }
      for (const row of rows) {
        const data = await parseData({ id: row.id, data: row });
        store.set({ id: row.id, data });
      }
    },
  };
}

Tu peux alors passer un filtre pour n’inclure que les posts d’une langue donnée.


2. Faire des jointures dans le loader

Tu peux utiliser les capacités de Drizzle pour faire des jointures SQL dans le loader, et ainsi enrichir chaque entrée avec les infos liées (auteur, commentaires, etc.) avant de les injecter dans le store.

Exemple :

import { db, posts, authors, comments } from './drizzle';
#

export function postsWithRelationsLoader(lang) {
return {
name: 'posts-with-relations-loader',
load: async ({ store, logger, parseData }) => {
logger.info('Chargement des posts avec auteurs et commentaires');
// Exemple de jointure avec Drizzle
const rows = await db.select()
.from(posts)
.innerJoin(authors, posts.authorId.eq(authors.id))
.leftJoin(comments, posts.id.eq(comments.postId))
.where(posts.language.eq(lang));
// Regroupe les commentaires par post, etc.
// ... (logique d’agrégation selon ton besoin)
for (const row of rows) {
const data = await parseData({ id: row.posts.id, data: {
...row.posts,
author: row.authors,
comments: row.comments ? [row.comments] : [],
}});
store.set({ id: row.posts.id, data });
}
},
};
}

> **Remarque** : la logique d’agrégation dépend de la structure de tes résultats Drizzle.

---

### 3. Pour un single post (getEntry)

Astro appelle le loader pour remplir la collection : tu peux choisir de ne charger qu’un sous-ensemble de données (par exemple, un seul post avec ses relations) si tu veux optimiser la mémoire, mais la logique de `getEntry` côté Astro reste la même.  
**Astro ne permet pas de loader “à la demande” par défaut** : tout ce que tu mets dans le store est accessible via `getEntry` ou `getCollection` ensuite.

---

### 4. Limites et alternatives
#
  • Astro Content Collections sont conçues pour charger les données à build time, pas à la demande. Si tu veux du “live” ou du SSR, regarde du côté des live content collections (expérimental), qui permettent de requêter dynamiquement à chaque requête HTTP.
  • Pour des besoins très dynamiques, tu peux aussi faire tes requêtes Drizzle directement dans tes pages Astro (hors content collections).

En résumé :
Oui, tu peux préparer tes requêtes, faire des jointures et filtrer dans le loader custom, avant d’injecter les données dans le content layer Astro. C’est la bonne pratique pour éviter de charger toutes les données inutilement et pour enrichir tes entrées avec les infos liées dès le départ.
Astro te donne toute la liberté dans le code du loader pour faire ce que tu veux avant de remplir le store de la collection !
Voir la doc sur les object loaders et le Content Loader API
Voir la doc sur les live content collections (expérimental)

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine pas de problèmes? Peut on faire plus performant?

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Astro te permet effectivement de préparer tes requêtes et de faire des jointures ou du filtrage dans un custom loader, ce qui est la méthode recommandée pour éviter de charger inutilement toutes les données et pour enrichir tes entrées dès le départ. Cette approche est conforme à la philosophie du Content Loader API d’Astro, qui te donne la liberté de contrôler comment et quelles données sont chargées dans tes collections : tu peux filtrer, agréger, ou joindre tes données côté loader avant de les injecter dans le store de la collection Voir la documentation sur les custom loaders.

En termes de performance, c’est la méthode la plus efficace possible dans le cadre des content collections Astro : tu ne charges que ce dont tu as besoin, tu peux faire des jointures SQL côté base, et tu profites de la validation et du typage automatique via Zod. Il n’existe pas, dans la documentation officielle, de méthode plus performante ou plus “native” pour ce type d’intégration. Si tu as des besoins de données encore plus dynamiques ou en temps réel, Astro recommande d’utiliser les live content collections (expérimental) ou de faire des requêtes directes dans tes pages avec on-demand rendering.

En résumé :

  • Pas de problème à préparer tes requêtes et à faire des jointures dans le loader, c’est même recommandé.
  • C’est la méthode la plus performante documentée pour ce cas d’usage avec Astro content collections.
  • Pour des besoins encore plus dynamiques, regarde du côté des live content collections ou du SSR classique.
#

Si tu restes dans le cadre des content collections, tu es déjà sur la solution la plus performante proposée par Astro aujourd’hui.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine Plus tard je compte faire un crud basé sur roles via middleware et better-auth. Comment faire pour que la logique fonctionne et continue ? J’ai déjà tout setup sauf le crud justement lol. J’ai pensé à un composant AutoCrud qui se base sur la collection importe le niveau, gere les relations etc !

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Tu es sur la bonne voie : Astro permet d’intégrer l’authentification (par exemple avec Better Auth) et la gestion des rôles via middleware, ce qui est la base pour sécuriser un CRUD. Voici comment tu peux structurer la suite pour que ta logique reste cohérente et maintenable :


1. Sécurisation des actions CRUD via middleware et Better Auth

  • Utilise le middleware Astro pour vérifier le rôle de l’utilisateur à chaque requête. Par exemple, tu peux interroger la session utilisateur via Better Auth dans le middleware, et rediriger/refuser l’accès si le rôle n’est pas suffisant.
import { auth } from "../../../auth"; // ton instance Better Auth
import { defineMiddleware } from "astro:middleware";

export const onRequest = defineMiddleware(async (context, next) => {
  const session = await auth.api.getSession({ headers: context.request.headers });
  // Exemple : accès restreint à /admin
  if (context.url.pathname.startsWith("/admin") && session?.user?.role !== "admin") {
    return context.redirect("/");
  }
  return next();
});

Voir la doc sur l’utilisation de Better Auth et le middleware


2. Sécurisation des actions côté serveur

  • Pour chaque action (add, update, delete), vérifie le rôle dans le handler de l’action. Astro recommande de faire la vérification dans le handler pour chaque action, pas seulement dans le middleware, afin d’éviter les contournements :
import { defineAction, ActionError } from 'astro:actions';
#

export const server = {
deletePost: defineAction({
handler: async (_input, context) => {
if (!context.locals.user || context.locals.user.role !== "admin") {
throw new ActionError({ code: 'UNAUTHORIZED' });
}
// ... logique de suppression
}
})
}

[Voir la doc sur la sécurité des actions](<https://docs.astro.build/en/guides/actions/#security-when-using-actions>)

---

### 3. **Composant AutoCrud**

- Tu peux tout à fait créer un composant “AutoCrud” qui s’appuie sur la collection, gère les relations et adapte l’UI selon le rôle de l’utilisateur. Ce composant peut utiliser les helpers Astro (`getCollection`, `getEntry`, etc.) pour afficher les données, et appeler les actions serveur pour le CRUD.
- Pour l’UI, tu peux conditionner l’affichage des boutons (ajout, édition, suppression) selon le rôle de l’utilisateur récupéré via Better Auth.

---

### 4. **Gestion des relations**

- Dans ton composant, tu peux charger les collections liées (ex : auteurs, catégories) et les afficher dans des listes déroulantes ou des champs liés.
- Si tu utilises des schémas avec `reference()`, tu peux aussi utiliser `getEntry()` pour récupérer les données liées proprement.

---

### 5. **Résumé**

- **Middleware** : vérifie le rôle à chaque requête.
- **Actions serveur** : refais la vérification pour chaque action CRUD.
- **Composant AutoCrud** : adapte l’UI et la logique selon le rôle et les relations.
- **Astro + Better Auth** : tout est compatible et documenté pour ce workflow.
#

@stark lark Kapa.ai is still learning and improving, please let me know how I did by reacting below.

stark lark
#

@crimson ermine donc je touche ni au loader ni au content config?

crimson ermineBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Oui, d’après la documentation Astro, la gestion des rôles, de l’authentification et des autorisations pour un CRUD se fait principalement via le middleware et dans les handlers d’actions côté serveur, pas dans le loader ni dans le fichier de configuration des content collections. Le loader et le content config servent uniquement à charger et valider les données de tes collections, pas à gérer la logique d’accès ou de sécurité.

La documentation recommande explicitement de faire les vérifications d’autorisation dans les handlers d’actions et/ou dans le middleware, comme illustré ici :

Astro recommande d’autoriser les sessions utilisateur dans le handler d’action pour respecter les niveaux de permission et le rate-limiting par action. Cependant, tu peux aussi filtrer les requêtes à toutes les actions (ou à un sous-ensemble) depuis le middleware.
Security when using actions

Donc, pour la gestion des rôles et l’accès CRUD, tu n’as pas besoin de modifier ton loader ou ton content config. Concentre-toi sur le middleware et les handlers d’actions pour appliquer ta logique de sécurité.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: