Volver al blog

Arquitectura Serverless en 2025: Por Qué Tu Próxima API Debería Ser Serverless

Hola HaWkers, ¿todavía mantienes servidores corriendo 24/7 esperando requisiciones? ¿Pagas por capacidad que no usas 90% del tiempo? ¿Pasas madrugadas configurando auto-scaling?

Serverless cambió el juego. En 2025, la pregunta no es más "¿debo usar serverless?", sino "¿por qué todavía no estoy usando?". Voy a mostrarte exactamente cómo funciona, cuándo usar, y código real de producción.

¿Qué Es Serverless (De Verdad)?

Serverless NO significa "sin servidores". Significa que tú no gestionas servidores.

Modelo tradicional:

  • Tú provisionas EC2/VPS
  • Instalas Node.js, PM2, nginx
  • Configuras load balancer, auto-scaling
  • Monitoreas uso de CPU/RAM
  • Pagas 24/7, incluso sin tráfico

Modelo serverless:

  • Tú escribes función
  • Deploy con un comando
  • Plataforma gestiona todo
  • Escala automáticamente (0 a millones de requisiciones)
  • Pagas apenas por ejecución real

Principales Plataformas en 2025

1. AWS Lambda (El Gigante)

  • Mayor ecosistema (integración con 200+ servicios AWS)
  • Soporta Node.js, Python, Go, Java, .NET, Rust
  • Cold start: ~100-200ms (mejoró mucho)
  • Pricing: $0.20 por 1M requisiciones + compute time

2. Vercel Functions (El Dev-Friendly)

  • Deploy integrado con Git
  • Edge network global
  • Perfecto para Next.js/React
  • Cold start: ~50ms
  • Pricing: 100k invocaciones gratis, después $0.40/1M

3. Cloudflare Workers (El Más Rápido)

  • Edge computing (corre en 300+ ciudades)
  • Cold start: ~0ms (siempre "caliente")
  • V8 isolates (no containers)
  • Pricing: 100k/día gratis, después $0.50/1M

4. Netlify Functions (El Simple)

  • Integración perfecta con JAMstack
  • Deploy automático
  • Good for startups
  • Pricing: 125k/mes gratis

Tu Primera Function: Hello World Real

AWS Lambda + API Gateway:

// handler.js
exports.handler = async (event) => {
  const { name = 'World' } = event.queryStringParameters || {};

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify({
      message: `Hello, ${name}!`,
      timestamp: new Date().toISOString(),
      requestId: event.requestContext.requestId
    })
  };
};

Deploy con Serverless Framework:

# serverless.yml
service: hello-world-api

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1

functions:
  hello:
    handler: handler.handler
    events:
      - httpApi:
          path: /hello
          method: get

# Deploy con un comando
# serverless deploy

Resultado: API corriendo en https://xxxxxxx.execute-api.us-east-1.amazonaws.com/hello?name=Jeff

Serverless Computing

Caso Real: API de E-commerce

Vamos a construir API completa de productos con CRUD, validación y cache.

// api/products/index.js
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async (event) => {
  const { httpMethod, pathParameters, body } = event;

  try {
    switch (httpMethod) {
      case 'GET':
        return pathParameters?.id
          ? await getProduct(pathParameters.id)
          : await listProducts();

      case 'POST':
        return await createProduct(JSON.parse(body));

      case 'PUT':
        return await updateProduct(pathParameters.id, JSON.parse(body));

      case 'DELETE':
        return await deleteProduct(pathParameters.id);

      default:
        return response(405, { error: 'Method not allowed' });
    }
  } catch (error) {
    console.error('Error:', error);
    return response(500, { error: error.message });
  }
};

// Helper: Respuesta estandarizada
function response(statusCode, data) {
  return {
    statusCode,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify(data)
  };
}

// GET /products
async function listProducts() {
  const command = new ScanCommand({
    TableName: process.env.PRODUCTS_TABLE
  });

  const result = await docClient.send(command);

  return response(200, {
    products: result.Items,
    count: result.Count
  });
}

// GET /products/:id
async function getProduct(id) {
  const { GetCommand } = await import('@aws-sdk/lib-dynamodb');

  const command = new GetCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Key: { id }
  });

  const result = await docClient.send(command);

  if (!result.Item) {
    return response(404, { error: 'Product not found' });
  }

  return response(200, result.Item);
}

// POST /products
async function createProduct(data) {
  const { PutCommand } = await import('@aws-sdk/lib-dynamodb');

  // Validación
  const errors = validateProduct(data);
  if (errors.length > 0) {
    return response(400, { errors });
  }

  const product = {
    id: crypto.randomUUID(),
    ...data,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  };

  const command = new PutCommand({
    TableName: process.env.PRODUCTS_TABLE,
    Item: product
  });

  await docClient.send(command);

  return response(201, product);
}

// Validación de producto
function validateProduct(data, isUpdate = false) {
  const errors = [];

  if (!isUpdate && !data.name) {
    errors.push('Name is required');
  }

  if (data.name && data.name.length < 3) {
    errors.push('Name must be at least 3 characters');
  }

  if (!isUpdate && data.price === undefined) {
    errors.push('Price is required');
  }

  if (data.price !== undefined && (data.price < 0 || isNaN(data.price))) {
    errors.push('Price must be a positive number');
  }

  return errors;
}

Optimizaciones Esenciales

1. Reducir Cold Starts:

// ❌ Malo: Import dentro de la función
export const handler = async () => {
  const AWS = require('aws-sdk'); // Import a cada ejecución
  const db = new AWS.DynamoDB.DocumentClient();
  // ...
};

// ✅ Bueno: Import en el tope (reutilizado)
const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async () => {
  // docClient ya está instanciado
};

// ✅ Aún mejor: Lazy loading
let docClient;

function getDocClient() {
  if (!docClient) {
    const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
    const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
    docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
  }
  return docClient;
}

2. Provisioned Concurrency (Para APIs críticas):

# serverless.yml
functions:
  api:
    handler: handler.handler
    provisionedConcurrency: 2 # Siempre 2 instancias "calientes"
    reservedConcurrency: 100 # Limita concurrencia máxima

3. Caching Inteligente:

// Cache en memoria (persiste entre invocaciones de la misma instancia)
let cachedProducts = null;
let cacheExpiry = 0;

async function getProductsCached() {
  const now = Date.now();

  if (cachedProducts && now < cacheExpiry) {
    console.log('Cache hit!');
    return cachedProducts;
  }

  console.log('Cache miss, fetching...');
  const products = await fetchProductsFromDB();

  cachedProducts = products;
  cacheExpiry = now + (5 * 60 * 1000); // Cache por 5 minutos

  return products;
}

Costos Reales: Comparación

Escenario: API con 1M requisiciones/mes, 200ms promedio

Solución Costo/mes Detalles
EC2 t3.small (24/7) $17 + $5 Load Balancer = $22
AWS Lambda $0.40 $0.20 req + $0.20 compute
Vercel Functions $0.40 Después de 100k gratis
Cloudflare Workers $5 Plan pago ($5/mes)

Ventaja serverless: Ahorra 80-90% en tráfico bajo/medio.

Cuando serverless se vuelve caro:

  • Tráfico constante alto (>10M req/mes)
  • Funciones de larga duración (>15min)

Cuándo NO Usar Serverless

Evita para:

  1. WebSockets persistentes (usa EC2/ECS)
  2. Procesamiento pesado (ML training, video encoding)
  3. Latencia crítica (<10ms consistente)
  4. Estado persistente en memoria (caches grandes)

Alternativa híbrida:

// API serverless + Worker tradicional
// Lambda para endpoints
export const api = async (event) => {
  // Procesa requisición leve
  // Para tareas pesadas, envía para cola
  await sqs.sendMessage({
    queueUrl: WORKER_QUEUE,
    body: JSON.stringify({ task: 'heavy-compute' })
  });
};

// EC2 worker procesa cola
// Corre 24/7, optimizado para tareas pesadas

El Futuro del Serverless

Tendencias 2025:

  • Edge computing (Cloudflare Workers, Vercel Edge)
  • Serverless containers (AWS Fargate, Cloud Run)
  • Streaming responses (datos progresivos)
  • IA integrada (embeddings, summarization)

Para entender mejor patrones asíncronos esenciales en serverless, confiere Descubriendo el Poder del Async/Await en JavaScript.

¡Vamos a por ello! 🦅

Domina JavaScript de Verdad

El conocimiento que adquiriste en este artículo es solo el comienzo. Hay técnicas, patrones y prácticas que transforman desarrolladores principiantes en profesionales solicitados.

Invierte en Tu Futuro

Preparé un material completo para que domines JavaScript:

Formas de pago:

  • $9.90 USD (pago único)

Ver Contenido Completo

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios