B

GrowMetrics API

Elysia · Bun · TypeScript — API REST de alto rendimiento para analítica de campañas

Bun 1.1 Elysia 1.3 TypeScript 5.5 WebSocket JWT Auth
142K
req/s (benchmark)
0.7ms
latencia p99
100%
type-safe (Eden)
14
endpoints + WS
Auto
OpenAPI/Swagger
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

api.growmetrics.io/swagger
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