Base de données
PostgreSQL piloté par Drizzle, un schéma en modules avec des identifiants ULID.
La base est PostgreSQL 18, pilotée par Drizzle dans le package packages/db. Le schéma est découpé en modules, un fichier par domaine, tout réexporté depuis un index unique.
Le schéma en modules
packages/db/src/schema/
auth.ts # tables Better Auth (identifiants texte)
identity.ts # profile
content.ts # post, media
social.ts # follow, block, mute, reaction
safety.ts # report, moderation_action, protection_event, audit_log
notifications.ts # notification
index.ts # réexporte toutLe client Drizzle reçoit l'ensemble du schéma, ce qui rend toutes les tables disponibles pour les requêtes.
// packages/db/src/index.ts
import { drizzle } from "drizzle-orm/node-postgres";
import * as schema from "./schema";
export function createDb() {
return drizzle(env.DATABASE_URL, { schema });
}Identifiants ULID
Les tables métier utilisent des identifiants ULID, triables dans le temps et adaptés à la pagination par curseur. Les tables de Better Auth gardent leurs propres identifiants texte.
import { ulid } from "ulidx";
import { text } from "drizzle-orm/pg-core";
export const ulidPk = () =>
text("id").primaryKey().$defaultFn(() => ulid());Les index qui comptent
Pour un réseau social, seuls quelques index sont vraiment structurants. Ils sont posés dès le départ.
| Table | Index | Usage |
|---|---|---|
post | (author_id, created_at) | Fil de profil, chemin le plus sollicité |
follow | (follower_id, followee_id) et (followee_id) | Fil et listes, dans les deux sens |
report | (status, priority, created_at) | File de modération, triée comme une queue |
reaction | (post_id) | Compteurs |
notification | (user_id, created_at) | Fil de notifications |
Migrations
Le schéma se traduit en SQL via drizzle-kit.
bun run db:generate # génère la migration à partir du schéma
bun run db:push # applique sur la base
bun run db:studio # interface d'explorationLe garde-fou de la pagination
Le fil d'actualité est le chemin le plus sollicité. On pagine toujours par curseur (l'ULID de la dernière ligne), jamais avec OFFSET. Un OFFSET 10000 scanne dix mille lignes pour les jeter, ce qui s'effondre en quelques semaines d'usage. Les identifiants ULID triables fournissent ce curseur gratuitement.