Construir APIs Serverless con API Gateway
Cómo diseñar, desplegar y gestionar APIs HTTP serverless usando AWS API Gateway, Lambda y patrones de function-as-a-service.
Visión general
La computación serverless te permite ejecutar código sin provisionar ni gestionar servidores. AWS Lambda ejecuta tus funciones en respuesta a eventos, y API Gateway provee la puerta de entrada HTTP que enruta las peticiones a esas funciones. Juntas forman una plataforma de API que escala automáticamente y se paga por petición, eliminando el costo de servidores ociosos.
Esta arquitectura es ideal para APIs con tráfico variable o impredecible. Una startup podría servir cien peticiones por día en el lanzamiento y un millón por día seis meses después — serverless maneja ambos sin planificación de capacidad. Los trade-offs son la latencia de cold start (el retraso cuando una función despierta tras estar inactiva) y herramientas específicas del proveedor que crean vendor lock-in.
Cuándo usarlo
- Construyes APIs con patrones de tráfico esporádicos o impredecibles. Consulta Rate Limiting para proteger APIs bajo carga.
- Prototipas productos donde el servidor debería costar cero mientras nadie lo usa.
- Procesas webhooks, uploads de archivos o eventos programados vía HTTP. Consulta Input Validation para validar peticiones entrantes.
- Creas microservicios donde cada endpoint tiene necesidades de recursos diferentes.
No la uses para APIs con tráfico alto y sostenido, donde un contenedor cálido sale más barato, ni para trabajo de larga duración por encima del límite de 15 minutos de Lambda: eso va a ECS/Fargate o a un sistema batch.
Solución
AWS Lambda Handler (Python)
import json
def lambda_handler(event, context):
# API Gateway pasa los datos HTTP en el objeto event
method = event['httpMethod']
path = event['path']
query = event.get('queryStringParameters', {})
if method == 'GET' and path == '/users':
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'users': ['alice', 'bob']})
}
if method == 'POST' and path == '/users':
body = json.loads(event['body'])
return {
'statusCode': 201,
'body': json.dumps({'id': '123', 'name': body['name']})
}
return {'statusCode': 404, 'body': json.dumps({'error': 'Not found'})}
AWS Lambda Handler (Node.js)
exports.handler = async (event) => {
const { httpMethod, path, body } = event;
if (httpMethod === 'GET' && path === '/users') {
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ users: ['alice', 'bob'] })
};
}
if (httpMethod === 'POST' && path === '/users') {
try {
const data = JSON.parse(body);
return {
statusCode: 201,
body: JSON.stringify({ id: '123', name: data.name })
};
} catch {
return { statusCode: 400, body: JSON.stringify({ error: 'Invalid JSON body' }) };
}
}
return { statusCode: 404, body: JSON.stringify({ error: 'Not found' }) };
};
Sin ese try/catch, un body malformado lanza una excepción y API Gateway devuelve un 502 genérico: un 400 le dice al cliente qué hizo mal.
Despliegue con Terraform
Una API funcional necesita cinco piezas que los ejemplos mínimos suelen saltarse: el rol IAM, la integración AWS_PROXY, el permiso que permite a API Gateway invocar la función, y un despliegue con su stage.
resource "aws_iam_role" "lambda_role" {
name = "users-lambda-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy_attachment" "basic" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
resource "aws_lambda_function" "handler" {
function_name = "users-handler"
handler = "index.handler"
runtime = "nodejs20.x"
filename = "function.zip"
source_code_hash = filebase64sha256("function.zip")
role = aws_iam_role.lambda_role.arn
}
resource "aws_api_gateway_rest_api" "api" {
name = "users-api"
}
resource "aws_api_gateway_resource" "users" {
rest_api_id = aws_api_gateway_rest_api.api.id
parent_id = aws_api_gateway_rest_api.api.root_resource_id
path_part = "users"
}
resource "aws_api_gateway_method" "get_users" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.users.id
http_method = "GET"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "get_users" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.users.id
http_method = aws_api_gateway_method.get_users.http_method
type = "AWS_PROXY"
integration_http_method = "POST"
uri = aws_lambda_function.handler.invoke_arn
}
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.handler.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/*/*"
}
resource "aws_api_gateway_deployment" "deploy" {
rest_api_id = aws_api_gateway_rest_api.api.id
stage_name = "prod"
depends_on = [aws_api_gateway_integration.get_users]
}
output "invoke_url" {
value = "https://${aws_api_gateway_rest_api.api.id}.execute-api.${var.region}.amazonaws.com/prod"
}
AWS_PROXY es la decisión clave aquí: reenvía la petición HTTP completa a Lambda y espera la forma de respuesta {statusCode, headers, body} que devuelven los handlers — sin mapping templates que mantener.
Si aplicas esto y el endpoint devuelve 500 Internal Server Error, el sospechoso habitual es el aws_lambda_permission ausente: API Gateway necesita permiso explícito para invocar la función y el error que devuelve no lo dice. Revisa aws lambda get-policy en la función, y de paso busca Execution failed due to configuration error en los access logs de API Gateway.
Explicación
- API Gateway: Recibe peticiones HTTP, maneja la terminación TLS, throttling, caching y autenticación, y luego invoca la función Lambda con los datos de la petición en un objeto event estructurado.
- Lambda: Ejecución de funciones disparada por eventos, stateless por diseño. AWS gestiona el escalado — 1.000 peticiones simultáneas significan 1.000 instancias de la función en paralelo, y pagas por tiempo de ejecución y memoria, nada más.
- Cold starts: Cuando una función no ha sido invocada recientemente, AWS inicializa una instancia del runtime desde cero — cuenta con 100 ms a 2 s extra en esa primera llamada según el runtime y la memoria asignada. La concurrencia provisionada mantiene funciones calientes para endpoints sensibles a latencia — consulta Cold Start Optimization para la guía completa.
- Infrastructure as Code: Terraform, AWS SAM o el Serverless Framework expresan tus rutas de API, funciones, roles IAM y variables de entorno como configuración versionada en lugar de clics en la consola.
Variantes
| Plataforma | API Gateway | Runtime de función | Mejor para |
|---|---|---|---|
| AWS | API Gateway + Lambda | Python, Node, Java, Go | Plataforma madura, integraciones amplias |
| Azure | API Management + Functions | .NET, Node, Python | Ecosistema Microsoft, integración Visual Studio |
| GCP | Cloud Endpoints + Cloud Functions | Node, Python, Go | Integración BigQuery, precio competitivo |
API Gateway también soporta APIs WebSocket para tráfico bidireccional — consulta WebSockets en tiempo real — y Lambda authorizers para validación de tokens personalizada antes de que la petición llegue siquiera a tu handler.
Consideraciones de Producción
Las checklists genéricas para APIs serverless dicen siempre lo mismo; estas son las cosas que de verdad muerden en producción:
- Autenticación:
authorization = "NONE"vale para una demo y está mal para una API real. Adjunta un Lambda authorizer (ver la receta complementaria) o Cognito/IAM para cualquier cosa de cara al usuario; usa flujos OAuth 2.0 para clientes de terceros. - Secretos: nunca incrustes credenciales en el paquete de despliegue ni en variables de entorno en texto plano — usa AWS Secrets Manager (Azure Key Vault o Google Secret Manager en las otras nubes) y rótalos.
- Idempotencia: los gateways y los clientes reintentan las peticiones fallidas te guste o no — un POST que no es idempotente crea duplicados que luego tienes que explicar. Clavea las escrituras en un token de idempotencia suministrado por el cliente o un ID de deduplicación.
- Fallos parciales: con funciones disparadas por SQS, un solo registro malo falla el lote entero. Devuelve
batchItemFailurespara que solo se reintente el mensaje envenenado — y configura una dead letter queue para lo que sigue fallando. - Acceso a VPC: las funciones que tocan recursos privados necesitan configuración VPC, que cambia el comportamiento de cold start y cuesta un NAT gateway para salida a internet. No lo actives por defecto — añádelo cuando de verdad necesites subnets privadas.
- Testing: simula las llamadas al SDK de AWS para los unit tests (SAM CLI o LocalStack emulan API Gateway + Lambda en local), corre integration tests contra un stage desplegado de verdad, y prueba carga antes del lanzamiento — la promesa de “escala automáticamente” oculta límites de throttling y concurrencia que solo aparecen bajo tráfico real.
- Despliegues graduales: usa versiones y aliases de Lambda con canary traffic shifting (SAM lo soporta de forma nativa) en lugar de actualizar
$LATESTen caliente.
Lo que funciona
- Diseña sin estado: todo lo que tu función guarde en memoria puede desaparecer en la siguiente invocación — pasa el estado por la petición o por un almacén externo.
- Minimiza el tamaño del paquete de despliegue: los paquetes grandes aumentan el tiempo de cold start.
- Configura timeouts y memoria adecuados: la memoria escala la CPU proporcionalmente. Si una función es lenta, subir la memoria puede salir más barato que pagar más tiempo de ejecución a menos memoria.
- Usa variables de entorno para la configuración: las URLs de base de datos y los feature flags deberían configurarse vía variables de entorno, no incrustarse en el paquete.
- Implementa logging estructurado: escribe logs JSON con request IDs — CloudWatch Logs Insights puede consultarlos eficientemente para depurar.
- Usa dead letter queues: las invocaciones asíncronas fallidas se reintentan automáticamente. Una DLQ captura los fallos persistentes para inspeccionarlos y reprocesarlos.
Errores comunes
- Tratar Lambda como un servidor de larga duración: las funciones se cortan a los 15 minutos, a mitad del trabajo si hace falta. Mueve lo que tarde más a procesamiento batch (AWS Batch) o contenedores (ECS/Fargate).
- Ignorar los cold starts: una API de cara al usuario con 3 segundos de cold start da una experiencia terrible. Usa concurrencia provisionada o un ping de keep-alive en los endpoints sensibles a latencia.
- Sobre-provisionar memoria: la memoria escala linealmente con la factura, y la mayoría de los handlers jamás tocan los 1.024 MB que alguien tecleó el primer día. Perfila la tuya y asigna lo que realmente usa.
- Hardcodear credenciales: nunca hagas commit de claves AWS o contraseñas de base de datos en tu repositorio.
- No manejar fallos parciales: en procesamiento por lotes (triggers SQS), un solo registro malo puede tirar el lote entero.
Lectura adicional
- Docs de AWS — Building Lambda functions: la firma del handler y el modelo de runtime que siguen ambos ejemplos.
- Docs de AWS — API Gateway REST APIs: integraciones, stages y comportamiento proxy en profundidad.
- Docs de AWS — Lambda function scaling: concurrencia, throttling y mecánica de concurrencia provisionada.
- Especificación AWS SAM: el camino alternativo de IaC con canary deployments nativos.
- AWS Well-Architected — Serverless Lens: el marco del que se destila la sección Consideraciones de Producción.
Preguntas frecuentes
¿Cómo manejo las conexiones a base de datos en Lambda?
Usa connection pooling con un proxy ligero como RDS Proxy, o implementa tu propia lógica de reutilización de conexiones — abre la conexión una vez fuera del handler para que las invocaciones calientes la reaprovechen. Abrir una conexión nueva en cada invocación es lento y puede agotar el límite de conexiones de la base de datos.
¿Puedo ejecutar una aplicación full-stack en serverless?
Sí, pero evalúa los trade-offs. Los sitios estáticos y las APIs encajan de maravilla. Las conexiones WebSocket de larga duración o las sesiones stateful pueden servirse mejor con contenedores o EC2.
¿Es serverless más barato que los servidores tradicionales?
Todo se reduce a la forma de tu tráfico: con tráfico esporádico serverless sale barato porque pagas por petición, mientras que con tráfico alto y sostenido la balanza se inclina hacia contenedores provisionados o EC2. Modela tus volúmenes reales con la calculadora de precios de AWS antes de decidir — el punto de cruce sorprende a la mayoría de los equipos.
¿Cómo pruebo funciones Lambda en local?
Usa la AWS SAM CLI o el Serverless Framework para emular API Gateway y Lambda en local. Estas herramientas montan tu código en un contenedor Docker que reproduce el entorno de runtime de AWS.
¿Cuál es la diferencia entre las APIs REST y HTTP de API Gateway?
Las HTTP APIs son más nuevas, más baratas y más rápidas — soportan autorización OIDC/JWT y cuestan aproximadamente un tercio de las REST. Las REST APIs añaden funciones que las HTTP no tienen: API keys, usage plans, validación de peticiones, caching y endpoints privados. Para la mayoría de las APIs nuevas, empieza con HTTP API; recurre a REST cuando necesites esos extras.
¿Cómo añado autenticación a una API de API Gateway?
Tres caminos habituales: un Lambda authorizer (validación de tokens personalizada con tu propia lógica), Cognito user pools (identidad gestionada), o autenticación IAM para llamadas internas servicio a servicio. El enfoque de Lambda authorizer está cubierto en la receta complementaria.
Recursos Relacionados
Asegurar API Gateway con Lambda Authorizers Personalizados
Implementar Lambda authorizers personalizados para API Gateway con validacion JWT, generacion de politicas IAM y caching para autenticacion basada en tokens.
RecipeMinimizar la Latencia de Cold Start en Funciones Serverless
Cómo reducir tiempos de cold start en AWS Lambda, Azure Functions y Cloud Run usando concurrencia provisionada, lazy loading, tuning de runtime y optimización de dependencias.
RecipeArquitecturas Event-Driven con Lambda y EventBridge
Cómo diseñar sistemas débilmente acoplados usando funciones serverless disparadas por eventos de colas de mensajes, bases de datos y webhooks.
RecipeConstruir APIs en Tiempo Real con WebSockets en Serverless
Cómo implementar comunicación bidireccional en tiempo real usando WebSockets con AWS API Gateway, Lambda, DynamoDB y lo que funciona en gestión de conexiones.
RecipeLimitación de Tasa (Rate Limiting)
Cómo implementar rate limiting en APIs usando token bucket, sliding window y fixed window en Python, JavaScript y Java.
RecipeValidación de Input: Schemas en 3 Lenguajes
Valida el input de usuarios en el límite de la API con librerías de schemas. Ejemplos con Pydantic, Zod y Jakarta Validation, sanitización y errores estructurados.