Stack Bun / Elysia
Efesto gira sullo stack Bun / Elysia tramite
un entrypoint separato: efesto/elysia. Su questo stack il compito di Efesto è
ristretto: fornisce il routing da filesystem e monta i tuoi moduli. Tutto il resto
— validazione, OpenAPI, hook — è Elysia idiomatico e nativo.
L'OpenAPI è prodotto nativamente da @elysiajs/openapi,
non da YAML generati. Le rotte sono normale Elysia, oppure la classe BaseApiService di
Efesto, che rispecchia la convenzione per metodo di Express (_<verb>, _<verb>Swagger,
_<verb>Validation, _<verb>Multer). La validazione a runtime proviene da
TypeBox; non c'è auto-generazione di
swaggerModel né generazione di tipi .d.ts (TypeScript inferisce i tipi degli handler
dai tuoi schemi TypeBox).
Installazione
Elysia e il plugin OpenAPI sono peer dependency opzionali:
bun add efesto elysia @elysiajs/openapi
Crea il server
import efesto from "efesto/elysia";
const app = efesto({
isProduction: process.env.NODE_ENV === "production",
routesDir: `${import.meta.dir}/routes`,
prefix: "/api/v1",
swagger: true, // OpenAPI nativo su /api/v1/openapi
}).listen(2014);
console.log(`http://localhost:${app.server?.port}/api/v1`);
efesto(...) restituisce un'istanza Elysia nativa, quindi puoi anche montarla come
plugin: new Elysia().use(efesto(...)). L'oggetto di configurazione è documentato per
intero in Configurazione.
Scrivere le rotte
Ogni file di rotta fa export default di una di due forme. Possono essere mescolate
liberamente nello stesso progetto, ed entrambe compilano in rotte Elysia native, quindi
hanno prestazioni identiche.
- Native Elysia
- Class-based BaseApiService
Una semplice istanza Elysia che usa percorsi relativi. Efesto la monta al prefisso
ricavato dal file (routes/user/index.ts → /user):
import { Elysia, t } from "efesto/elysia";
const users = [{ id: 1, name: "Ada", surname: "Lovelace" }];
export default new Elysia()
.get("/", () => users, {
detail: { summary: "List users", tags: ["User"] },
})
.put(
"/",
({ body }) => {
const user = { id: Math.floor(Math.random() * 1000), ...body };
users.push(user);
return users;
},
{
body: t.Object({ name: t.String(), surname: t.String() }),
detail: { summary: "Create user", tags: ["User"] },
}
);
Una classe che estende BaseApiService, scritta esattamente come un BaseApiService
di Express: la stessa convenzione per metodo (_<verb>, _<verb>Swagger,
_<verb>Validation, _<verb>Multer), lo stesso DSL a schema di SwaggerOptions, la
stessa tupla permission. Solo due cose differiscono: l'handler usa la sintassi a
metodo con il contesto Elysia nativo (niente req, res), e la validazione a runtime è
TypeBox invece di express-validator.
import { BaseApiService, type Context, RouteValidation, SwaggerOptions, t } from "efesto/elysia";
const products = [{ id: 1, name: "Anvil" }];
export default class extends BaseApiService {
_getSwagger: SwaggerOptions = { operationId: "listProducts", permission: ["readAll", "Product"], summary: "List products" };
_get() {
return products;
}
_postSwagger: SwaggerOptions = { operationId: "createProduct", permission: ["create", "Product"] };
_postValidation: RouteValidation = { body: t.Object({ name: t.String() }) };
_post({ body }: Context) {
const product = { id: Math.floor(Math.random() * 1000), name: (body as { name: string }).name };
products.push(product);
return product;
}
}
È accettata anche una classe con nome con constructor() { super(__filename); } (per
parità con Express), ma è opzionale: il mount ricava comunque il percorso dalla
posizione del file. Per ogni verbo dichiari:
_<verb>— l'handler, sintassi a metodo_get(ctx) { ... }, contesto Elysia nativo, restituisce un valore._<verb>Validation— schemi TypeBox{ body, query, params, response }(tipizzatiRouteValidation); validazione a runtime compilata da Elysia e aggiunta all'OpenAPI._<verb>Swagger—SwaggerOptions:operationId,summary,requestBody,responses,parameters(il DSL dei Magic Types di Efesto, espanso in OpenAPI nativo) piùpermission: [action, model]per l'ABAC._<verb>Multer/_<verb>MultipleMulter(solopost/patch/put) — accettano upload multipart; il file arriva inctx.body. Vedi Upload di file.
Verbi supportati: get, post, put, patch, delete, head, options.
Annota _<verb>Swagger: SwaggerOptions e _<verb>Validation: RouteValidation come
nell'esempio: ciò mantiene permission inferito come tupla senza cast. Il ctx
dell'handler è il contesto Elysia nativo, quindi leggi l'input validato da
ctx.body/ctx.query/ctx.params (fai il cast o annota la forma che hai dichiarato in
_<verb>Validation).
Modelli condivisi
Dichiara schemi riutilizzabili una volta tramite l'opzione models; i riferimenti
@Model nel _<verb>Swagger di qualsiasi rotta si risolvono poi rispetto a essi
(riscritti in ref nativi #/components/schemas/Model e registrati come componenti
OpenAPI). I valori possono usare il DSL di Efesto o essere schemi semplici/TypeBox:
efesto({
isProduction: false,
routesDir: `${import.meta.dir}/routes`,
models: {
User: { properties: { name: "string", email: "string::email", friend: "@User?" } },
},
// swaggerConfig: { customTypes, customFormats } estende il DSL con i tuoi tipi
});
Aspetti trasversali
Componi auth, ABAC, caching e CORS nativamente tramite setup, che viene eseguito
sull'istanza root prima del mount delle rotte, così si applica a ogni rotta. Per le
regole permission basate su classe, fornisci qui l'ability CASL nel contesto:
import { defineAbility } from "@casl/ability";
efesto({
isProduction: false,
routesDir: `${import.meta.dir}/routes`,
setup: (root) => {
root
.derive(() => ({ ability: defineAbility((can) => can("readAll", "User")) }))
.onBeforeHandle(({ ability }) => {
// autentica / verifica i permessi, o lancia un'eccezione per rifiutare
});
},
});
Vedi Autenticazione e Permessi ABAC per i dettagli: entrambi hanno una tab Bun/Elysia.
Differenze rispetto allo stack Express
| Aspetto | Express (efesto) | Bun/Elysia (efesto/elysia) |
|---|---|---|
| File di rotta | class extends BaseApiService | new Elysia()... nativo oppure class extends BaseApiService |
| Handler | _get(req, res, next) + res.json(...) | _get(ctx) { return value } nativo |
| Validazione | _<verb>Validation (express-validator) | _<verb>Validation (schema TypeBox per rotta) |
| OpenAPI | .yaml generato da _<verb>Swagger | @elysiajs/openapi nativo, da TypeBox + il DSL Magic Types di _<verb>Swagger |
| Upload | _<verb>Multer / _<verb>MultipleMulter | uguale: _<verb>Multer / _<verb>MultipleMulter |
| Auth | authMiddleware globale + _<verb>OverrideAuth | nativo, via setup |
| Caching | Redis integrato (cache/purgeKey) | plugin Elysia nativi |
| Tipi | .d.ts generato (+ modifica magica) | inferiti nativamente da TypeScript |