My App
Backend

Elysia

Comment Vantablvck exploite l'écosystème Elysia, des macros de sécurité à l'OpenAPI généré.

Le point d'avoir choisi Elysia plutôt qu'un routeur minimal, c'est d'exploiter son écosystème. Elysia n'est pas utilisé comme un Express typé, il est utilisé pour ce qu'il fait de mieux : encoder les garde-fous dans le pipeline du framework.

Le serveur est encore un squelette. Cette page décrit le design cible et les patterns idiomatiques que le backend suivra module par module.

Un module, une instance

Chaque feature est une instance Elysia, composée à la racine via .use(). Les services sont des classes découplées du framework, les schémas de validation sont exportés à part. Une route délègue au service, le service ne touche pas au framework.

apps/server/src/
  modules/
    posts/
      index.ts      # instance Elysia, les routes
      service.ts    # logique métier, découplée
      model.ts      # schémas Zod partagés
  plugins/
    safety.ts       # les macros de sécurité
  index.ts          # composition racine

Les macros encodent la sécurité

C'est le vrai levier. Les garde-fous (authentification, niveau de confiance, limitation de débit) sont des macros, déclaratives et typées sur chaque route. La friction anti-abus et la proportionnalité vivent donc au niveau du framework, pas dispersées dans les handlers.

// apps/server/src/plugins/safety.ts
export const safety = new Elysia({ name: "safety" }).macro({
  auth: {
    resolve: async ({ request, status }) => {
      const session = await auth.api.getSession({ headers: request.headers });
      if (!session) return status(401, "Sign in required");
      return { user: session.user };
    },
  },
  requireTrust: (min: "new" | "active" | "trusted") => ({
    resolve: ({ user, status }) =>
      trustRank(user.trustLevel) < trustRank(min)
        ? status(403, "Not allowed yet")
        : {},
  }),
  rateLimit: (bucket: string) => ({
    beforeHandle: ({ user, status }) =>
      limiter.ok(bucket, user.id) ? undefined : status(429, "Slow down"),
  }),
});

Sur une route, tout devient déclaratif :

// apps/server/src/modules/posts/index.ts
export const posts = new Elysia({ prefix: "/posts" })
  .use(safety)
  .post("/", ({ user, body }) => PostService.create(user.id, body), {
    auth: true,
    requireTrust: "active", // les nouveaux comptes sont bridés
    rateLimit: "post:create",
    body: CreatePost, // schéma Zod partagé
  });

Un post qui exige d'être connecté, active au minimum, rate-limité, et validé. Le tout typé de bout en bout jusqu'au client Eden.

Validation avec Zod

Elysia supporte les Standard Schema, donc Zod se branche directement dans body, query et params. La règle du projet reste tenue : validation Zod sur toute entrée. Un seul schéma valide au runtime, produit le type, et alimente l'OpenAPI.

import { z } from "zod";

const CreatePost = z.object({
  body: z.string().max(500),
  replyToId: z.string().ulid().optional(),
});

Ce qu'on branche de l'écosystème

Plugin ou capacitéUsage
@elysiajs/openapiDocumentation d'API générée depuis les schémas, front Scalar. Les routes de modération sont exclues de la doc publique
@elysiajs/opentelemetryObservabilité dès le début, un garde-fou
WebSocket natifTemps réel : notifications, puis messages privés
t.File avec fileTypeUpload sécurisé : le vrai type est validé par magic number, pas seulement par le MIME
Eden TreatyTypes partagés du serveur vers les clients web et native

Le lifecycle au service de la safety

Elysia découpe une requête en étapes. On les exploite pour la sécurité, pas seulement pour router.

ÉtapeUsage
onRequestLimitation de débit globale
resolveInjection de l'utilisateur validé
onAfterResponseÉcriture du journal d'audit, déclenchement du calcul d'acharnement en asynchrone, sans bloquer la réponse
onErrorErreurs typées et messages clairs

Upload de média sécurisé

L'upload passe par t.File avec fileType, qui vérifie le vrai type du fichier par sa signature binaire. Cela sert directement le garde-fou du média : un fichier déguisé est rejeté, et rien n'est diffusé avant le scan complet.

On this page