GrowMetrics API — Generada con Elysia + Bun
API REST de alto rendimiento para gestión de campañas publicitarias. TypeBox valida cada input en tiempo de ejecución; Eden Treaty proporciona tipos automáticos al cliente Next.js — sin OpenAPI parsing, sin codegen.
Bun Runtime
142K req/s. Arranque en 38ms. Bun compila TypeScript nativamente, sin tsc ni ts-node.
Types extremo a extremo
El server define los schemas con TypeBox. Eden Treaty los infiere en el cliente Next.js. Cero DTOs duplicados.
OpenAPI automático
Swagger en
/swagger generado desde los schemas TypeBox. Sin decorators ni YAML manual.JWT con derive
Plugin
@elysiajs/jwt + derive() inyecta el usuario en todas las rutas protegidas con un guard compartido.WebSocket tipado
Pub/sub por
campaignId. Los dashboards de clientes reciben actualizaciones en tiempo real con mensajes validados por TypeBox.Rutas modulares
Grupos de rutas con
new Elysia({ prefix }). campaignRoutes, metricRoutes y authRoutes compuestos en el app principal.src/index.ts — Entry point
src/index.ts
TypeScript · Elysia
import { Elysia } from "elysia" import { cors } from "@elysiajs/cors" import { swagger } from "@elysiajs/swagger" import { jwt } from "@elysiajs/jwt" import { authRoutes } from "./routes/auth" import { campaignRoutes } from "./routes/campaigns" import { metricRoutes } from "./routes/metrics" import { wsRoutes } from "./routes/ws" const app = new Elysia() // ── Plugins globales .use(cors({ origin: ["https://app.growmetrics.io", "http://localhost:3000"], credentials: true, })) .use(swagger({ documentation: { info: { title: "GrowMetrics API", version: "1.0.0" }, tags: [ { name: "auth", description: "Autenticación de agencias" }, { name: "campaigns", description: "Gestión de campañas" }, { name: "metrics", description: "Métricas de rendimiento" }, ], }, })) .use(jwt({ name: "jwt", secret: process.env.JWT_SECRET!, exp: "7d", })) // ── Inject auth en todas las rutas .derive(async ({ jwt, headers, error }) => { const token = headers.authorization?.replace("Bearer ", "") if (!token) return { agency: null } const payload = await jwt.verify(token) return { agency: payload as { id: string; name: string } | null } }) // ── Rutas modulares .use(authRoutes) .use(campaignRoutes) .use(metricRoutes) .use(wsRoutes) .listen(4000) console.log(`🦊 GrowMetrics API → ${app.server?.url}`) export type App = typeof app // 🔑 Eden Treaty necesita este export
routes/campaigns.ts — CRUD tipado
src/routes/campaigns.ts
TypeScript · TypeBox validation
import { Elysia, t } from "elysia" // Schema reutilizable — una fuente de verdad para types + validación const CampaignSchema = t.Object({ name: t.String({ minLength: 3, maxLength: 120 }), client: t.String(), budget: t.Number({ minimum: 100, maximum: 1_000_000 }), status: t.Union([ t.Literal("active"), t.Literal("paused"), t.Literal("ended"), ]), startDate: t.String({ format: "date" }), endDate: t.Optional(t.String({ format: "date" })), }) export const campaignRoutes = new Elysia({ prefix: "/campaigns" }) // GET /campaigns?status=active&page=1&limit=20 .get("/", async ({ query, agency, error }) => { if (!agency) return error(401, { message: "Token requerido" }) const campaigns = await db.campaign.findMany({ where: { agencyId: agency.id, status: query.status }, skip: (query.page - 1) * query.limit, take: query.limit, }) return { data: campaigns, total: campaigns.length, page: query.page } }, { query: t.Object({ status: t.Optional(t.Union([t.Literal("active"), t.Literal("paused"), t.Literal("ended")])), page: t.Number({ default: 1 }), limit: t.Number({ default: 20, maximum: 100 }), }), tags: ["campaigns"], }) // POST /campaigns — crear campaña .post("/", async ({ body, agency, error }) => { if (!agency) return error(401, { message: "Token requerido" }) const campaign = await db.campaign.create({ data: { ...body, agencyId: agency.id, id: crypto.randomUUID() }, }) return { data: campaign } }, { body: CampaignSchema, tags: ["campaigns"], })
WebSocket /ws/metrics/:campaignId — Tiempo Real
export const wsRoutes = new Elysia({ prefix: "/ws" }) .ws("/metrics/:campaignId", { body: t.Object({ type: t.Union([t.Literal("subscribe"), t.Literal("ping")]), }), open(ws) { const { campaignId } = ws.data.params ws.subscribe(`campaign:${campaignId}`) ws.send({ type: "connected", campaignId }) }, message(ws, { type }) { if (type === "ping") ws.send({ type: "pong", ts: Date.now() }) }, }) // Servidor envía update cuando ingresan métricas nuevas: // app.server?.publish(`campaign:${id}`, JSON.stringify({ impressions, clicks, roas }))
🌿 Eden Treaty — Cliente Next.js 100% tipado
Sin codegen. El server exporta su tipo; Eden infiere todo automáticamente.
hooks/useGrowMetrics.ts
Next.js · Eden Treaty
import { treaty } from "@elysiajs/eden" import type { App } from "@growmetrics/api" // Solo el TYPE, nada de runtime const api = treaty<App>("api.growmetrics.io") // ✅ Tipado completo — response, params y body inferidos del server const { data, error } = await api.campaigns.get({ query: { status: "active", page: 1, limit: 20 }, headers: { authorization: `Bearer ${token}` }, }) // ^? { data: Campaign[], total: number, page: number } // POST con body validado en compile time const { data: created } = await api.campaigns.post({ name: "Black Friday 2026 — ElectroMax", client: "ElectroMax", budget: 48500, status: "active", // TS error si pones "activo" startDate: "2026-11-01", }, { headers: { authorization: `Bearer ${token}` } }) // ^? { data: { id: string, name: string, budget: number, ... } }
Swagger UI — auto-generado desde TypeBox schemas
GrowMetrics API
OAS 3.0 · version 1.0.0 · 14 endpoints · generado automáticamente
POST
/auth/login
Obtener JWT para una agencia
GET
/campaigns
Listar campañas con filtros y paginación
POST
/campaigns
Crear nueva campaña
GET
/campaigns/{id}
Detalle de campaña
PUT
/campaigns/{id}
Actualizar estado o presupuesto
GET
/campaigns/{id}/metrics
Series temporales de métricas
GET
/campaigns/{id}/summary
ROAS, CTR, conversiones totales
DEL
/campaigns/{id}
Eliminar campaña (admin)
Ejemplo de respuestas
GET
/campaigns/a3f7b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c/summary
Resumen de campaña
200 OK
{
"data": {
"campaignId": "a3f7b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"name": "Black Friday 2026 — ElectroMax",
"status": "active",
"budget": 48500,
"spent": 31240.80,
"summary": {
"impressions": 2847392,
"clicks": 84621,
"conversions": 1847,
"ctr": 2.97,
"roas": 4.82,
"cpa": 16.91
},
"lastUpdated": "2026-06-16T08:42:00Z"
}
}
Skill: elysia-api-bun-typescript
·
Cliente: GrowMetrics SaaS B2B
·
Generado por CULTIVA IA Arsenal
Bun 1.1 · Elysia 1.3 · TypeScript 5.5 · Eden Treaty