Volver al blog

Serverless con JavaScript: Arquitectura Moderna y Escalable para 2025

Hola HaWkers, ¿ya te cansaste de gestionar servidores, configurar auto-scaling y preocuparte con infraestructura cuando solo quieres entregar features?

Serverless llegó para resolver exactamente este problema. En 2025, la arquitectura serverless con JavaScript/Node.js se convirtió en uno de los patrones más adoptados por empresas de todos los tamaños. Desde startups hasta enterprises, todos están migrando workloads para funciones serverless.

¿Qué Es Serverless y Por Qué Importa?

Serverless no significa "sin servidores" — significa que tú no gestionas servidores. El proveedor cloud cuida de toda la infraestructura: provisioning, scaling, patching, monitoring.

Tu enfoque queda 100% en el código que entrega valor.

Beneficios principales:

  • Escala automática: De 0 a millones de requisiciones sin configurar nada
  • Pay-per-use: Pagas apenas por el tiempo de ejecución, no por servidor idle
  • Zero ops: Sin gestión de servidores, containers o clusters
  • Deploy rápido: Funciones pequeñas = deploys en segundos
  • Alta disponibilidad: Replicación automática en múltiples zonas

AWS Lambda con Node.js: El Estándar de la Industria

AWS Lambda es la plataforma serverless más utilizada del mundo. Con Node.js, puedes construir desde APIs simples hasta pipelines de procesamiento de datos complejos.

// handler.js - Función Lambda básica
export const handler = async (event, context) => {
  // event contiene los datos de la requisición
  const { httpMethod, body, queryStringParameters } = event;

  try {
    // Procesar requisición
    const data = JSON.parse(body || '{}');

    // Lógica de negocio
    const result = await processData(data);

    // Respuesta exitosa
    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({
        success: true,
        data: result
      })
    };
  } catch (error) {
    console.error('Error:', error);

    return {
      statusCode: 500,
      body: JSON.stringify({
        success: false,
        error: error.message
      })
    };
  }
};

async function processData(data) {
  // Tu lógica de negocio aquí
  return { processed: true, timestamp: new Date().toISOString() };
}

Configuración con Serverless Framework:

# serverless.yml
service: mi-api-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 256
  timeout: 10

functions:
  api:
    handler: handler.handler
    events:
      - httpApi:
          path: /api/{proxy+}
          method: ANY

  processQueue:
    handler: queue-handler.process
    events:
      - sqs:
          arn: !GetAtt ProcessingQueue.Arn
          batchSize: 10

resources:
  Resources:
    ProcessingQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: processing-queue

Vercel Functions: Simplicidad para Frontend Developers

Para desarrolladores frontend, Vercel Functions ofrece la experiencia más simple de serverless. Crea un archivo en /api y ya tienes una función funcionando.

// api/users/[id].js
export default async function handler(req, res) {
  const { id } = req.query;
  const { method } = req;

  switch (method) {
    case 'GET':
      // Buscar usuario
      const user = await getUserById(id);

      if (!user) {
        return res.status(404).json({ error: 'Usuario no encontrado' });
      }

      return res.status(200).json(user);

    case 'PUT':
      // Actualizar usuario
      const updated = await updateUser(id, req.body);
      return res.status(200).json(updated);

    case 'DELETE':
      // Eliminar usuario
      await deleteUser(id);
      return res.status(204).end();

    default:
      res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
      return res.status(405).end(`Method ${method} Not Allowed`);
  }
}

// Funciones auxiliares
async function getUserById(id) {
  // Conectar al banco de datos
  const { db } = await import('../../lib/db');
  return db.users.findUnique({ where: { id } });
}

Patrones de Arquitectura Serverless

1. API Gateway + Lambda

El patrón más común: API Gateway recibe HTTP requests y dispara funciones Lambda.

// api/products.js
import { DynamoDB } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';

const db = DynamoDBDocument.from(new DynamoDB());
const TABLE_NAME = process.env.PRODUCTS_TABLE;

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

  switch (httpMethod) {
    case 'GET':
      if (pathParameters?.id) {
        return await getProduct(pathParameters.id);
      }
      return 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);
  }
};

async function listProducts() {
  const result = await db.scan({ TableName: TABLE_NAME });

  return {
    statusCode: 200,
    body: JSON.stringify(result.Items)
  };
}

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

  await db.put({
    TableName: TABLE_NAME,
    Item: product
  });

  return {
    statusCode: 201,
    body: JSON.stringify(product)
  };
}

2. Event-Driven Architecture

Funciones disparadas por eventos: S3, SQS, SNS, EventBridge.

// image-processor.js
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';

const s3 = new S3Client();

export const handler = async (event) => {
  // Evento S3 cuando imagen es subida
  for (const record of event.Records) {
    const bucket = record.s3.bucket.name;
    const key = decodeURIComponent(record.s3.object.key);

    console.log(`Procesando imagen: ${bucket}/${key}`);

    try {
      // Descargar imagen original
      const { Body } = await s3.send(new GetObjectCommand({
        Bucket: bucket,
        Key: key
      }));

      const imageBuffer = await streamToBuffer(Body);

      // Crear thumbnail
      const thumbnail = await sharp(imageBuffer)
        .resize(200, 200, { fit: 'cover' })
        .jpeg({ quality: 80 })
        .toBuffer();

      // Subir thumbnail
      const thumbnailKey = key.replace('uploads/', 'thumbnails/');

      await s3.send(new PutObjectCommand({
        Bucket: bucket,
        Key: thumbnailKey,
        Body: thumbnail,
        ContentType: 'image/jpeg'
      }));

      console.log(`Thumbnail creado: ${thumbnailKey}`);
    } catch (error) {
      console.error(`Error procesando ${key}:`, error);
      throw error;
    }
  }
};

async function streamToBuffer(stream) {
  const chunks = [];
  for await (const chunk of stream) {
    chunks.push(chunk);
  }
  return Buffer.concat(chunks);
}

3. Scheduled Functions (Cron Jobs)

// cleanup-job.js
export const handler = async (event) => {
  console.log('Ejecutando cleanup job:', new Date().toISOString());

  // Limpiar sesiones expiradas
  const expiredSessions = await cleanupExpiredSessions();
  console.log(`Sesiones eliminadas: ${expiredSessions}`);

  // Limpiar archivos temporales
  const tempFiles = await cleanupTempFiles();
  console.log(`Archivos temporales eliminados: ${tempFiles}`);

  // Enviar reporte
  await sendCleanupReport({
    sessionsDeleted: expiredSessions,
    filesDeleted: tempFiles,
    executedAt: new Date().toISOString()
  });

  return { success: true };
};
# serverless.yml
functions:
  cleanup:
    handler: cleanup-job.handler
    events:
      - schedule: rate(1 hour)  # Ejecutar cada hora

Cold Starts y Optimización de Performance

El mayor desafío de serverless es el "cold start" — el tiempo para inicializar una función que estaba idle.

// ❌ Malo: Imports pesados dentro de la función
export const handler = async (event) => {
  // Esto se ejecuta EN CADA invocación
  const mongoose = require('mongoose');
  await mongoose.connect(process.env.MONGODB_URI);

  // Procesar...
};

// ✅ Bueno: Imports y conexiones en el tope
import mongoose from 'mongoose';

// Conexión reutilizada entre invocaciones
let cachedConnection = null;

async function connectDB() {
  if (cachedConnection) return cachedConnection;

  cachedConnection = await mongoose.connect(process.env.MONGODB_URI);
  return cachedConnection;
}

export const handler = async (event) => {
  await connectDB();
  // Procesar...
};

// ✅ Aún mejor: Lazy loading para módulos pesados
let sharp;

export const handler = async (event) => {
  if (!sharp) {
    sharp = await import('sharp');
  }

  // Usar sharp apenas cuando necesario
};

Otras técnicas de optimización:

  • Provisioned Concurrency: Mantener funciones "calientes"
  • Bundle minification: Reducir tamaño del deployment
  • Layer caching: Compartir dependencias entre funciones

Costos y Cuándo Usar Serverless

Serverless no siempre es la mejor opción. Analiza tu caso de uso:

Ideal para:

  • APIs con tráfico variable
  • Procesamiento de eventos asíncrono
  • Cron jobs y tareas programadas
  • MVPs y prototipos rápidos
  • Microservicios independientes

Evitar para:

  • Aplicaciones con tráfico constante alto
  • Procesamiento de larga duración (>15 min)
  • Workloads que necesitan GPU
  • Sistemas real-time con WebSockets

Estimativa de costos (AWS Lambda):

1 millón de invocaciones/mes
+ 400.000 GB-segundos de compute
= ~$3.00 USD/mes

Comparado con EC2 t3.micro 24/7:
= ~$8.00 USD/mes

Ahorro: ~60% en este escenario

Si quieres profundizar en arquitectura moderna de aplicaciones JavaScript, recomiendo leer sobre Microfrontends: Arquitectura Escalable, donde exploramos cómo combinar serverless con frontend moderno.

¡Vamos a por ello! 🦅

¿Quieres Dominar 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