Seguridad de Tipos
Evita errores comunes como olvidar pasar el contexto a las capas inferiores.
Fang JS utiliza Decoradores e Interfaces para garantizar que tu código siga una arquitectura predecible. Su función principal es validar que tus clases tengan la estructura necesaria para integrarse con el motor de rutas y el sistema de contextos.
@ControllerEl decorador @Controller valida que tu clase sea un controlador legítimo. Para que un controlador sea válido en Fang JS, debe tener un método estático llamado register.
Este método es el encargado de recibir el RouteGroup y definir las rutas.
import { Controller, type RouteGroup } from "@fang-js/fang";
@Controllerexport class UserController { static register(group: RouteGroup) { group.get("/", (ctx) => ctx.ok("Lista de usuarios")); group.post("/create", (ctx) => ctx.created("Usuario creado")); }}Al registrarse, Fang lanzará un log informativo indicando que el controlador ha sido vinculado correctamente.
@ServiceEl decorador @Service asegura que tu lógica de negocio esté preparada para recibir el Contexto. En Fang JS, la regla de oro es: el primer argumento de cualquier método de servicio debe ser ctx: Context. Este decorador se auxilia de la clase BaseService.
import { Service, Context } from "@fang-js/fang";
@Serviceexport class UserService extends BaseService { async findAll(ctx: Context) { // ctx está disponible para acceder a query, params, etc. return { message: "Buscando datos..." }; }}BaseServiceEsta clase abstracta “obliga” a tus métodos a seguir la firma correcta, dándote autocompletado inmediato mientras escribes, esta clase debe extender a tu service.
import { BaseService, Context } from "@fang-js/fang";
// Tu editor te avisará si olvidas incluir 'ctx' como primer parámetroexport class PostService extends BaseService { async getPosts(ctx: Context, limit: number) { return []; }}Seguridad de Tipos
Evita errores comunes como olvidar pasar el contexto a las capas inferiores.
IntelliSense
Autocompletado instantáneo al implementar métodos en tus servicios.
Logs de Registro
Seguimiento claro de qué controladores se han cargado durante el arranque.
// 1. Definimos el Servicio y heredamos de la clase BaseServiceimport { Service, BaseService, Context } from "@fang-js/fang";
@Serviceexport class AuthService extends BaseService { async login(ctx: Context) { const body = await ctx.body(); return { token: "secret_wolf_token" }; }}
// 2. Definimos el Controlador con el método static registerimport { Controller, RouteGroup } from "@fang-js/fang";
@Controllerexport class AuthController { static register(group: RouteGroup) { const service = new AuthService();
group.post("/login", (ctx) => service.login(ctx)); }}
// 3. Registro en la Appapp.group("/auth", AuthController.register);@ReflectionControllerA diferencia del registro manual usando el decorador @Controller, el decorador @ReflectionController permite una arquitectura totalmente automatizada. Este decorador recibe un prefijo de ruta y marca la clase para que el motor de Autoloading de Fang JS la encuentre y registre automáticamente.
Para usarlo, define el prefijo de la ruta en el decorador y asegúrate de implementar el método estático register.
import { ReflectionController, type RouteGroup } from "@fang-js/fang";import { PeopleService } from "./people-service.js";
@ReflectionController("/people")export class PeopleController { static register(group: RouteGroup) { const peopleService = new PeopleService();
group.get("/", (ctx) => peopleService.getPeople(ctx)); }}Metadata: El decorador normaliza el path (asegurando que empiece con /) y lo guarda como metadatos ocultos en la clase.
Descubrimiento: Cuando llamas a autoloadControllers, Fang escanea tus archivos, busca estos metadatos y ejecuta el método register.
Aislamiento: Cada controlador se convierte en un grupo de rutas independiente, manteniendo tu main.ts limpio de importaciones masivas.
import { Fang } from "@fang-js/fang";import { dirname } from "node:path";import { fileURLToPath } from "node:url";
const app = new Fang();const __dirname = dirname(fileURLToPath(import.meta.url));
async function bootstrap() { // Escanea la carpeta actual y registra PeopleController automáticamente bajo /people await app.autoloadControllers(__dirname);
app.listen(3000);}
bootstrap();