Construyendo aplicaciones serverless con IA: el starter de AWS que uso para cada proyecto
Durante el último año he construido varias aplicaciones serverless full-stack en AWS. Y algo ha cambiado: con los agentes de IA para desarrollo, escribir la aplicación ya no es necesariamente la parte más lenta del proceso.
Cada vez consume más tiempo todo lo que rodea al código: cómo estructurar el repositorio, qué patrones seguir, cómo debe funcionar la autenticación, cómo organizar la infraestructura, cómo compartir tipos entre frontend y backend y cómo desplegarlo todo de forma consistente.
Antes de tener esto resuelto, cada proyecto nuevo significaba tomar de nuevo las mismas decisiones de arquitectura y conectar las mismas piezas. La IA ha hecho que este problema sea más visible, no menos. Si le das a un agente un codebase inconsistente, puede reproducir esa inconsistencia a gran velocidad.
Así que dejé de empezar desde cero.
Extraje la base común de mis aplicaciones en una plantilla reutilizable de GitHub y la publiqué como serverless-monorepo-aws-starter.
El problema ha cambiado
Construir una aplicación serverless full-stack sigue implicando integrar varias piezas:
- Autenticación con Amazon Cognito, MFA, roles y flujos de login
- Infraestructura como Código para Amazon DynamoDB, Amazon API Gateway, AWS Lambda, Amazon CloudFront y Amazon S3
- CI/CD para linting, tests, build y despliegue
- Un frontend con routing, estado de autenticación y soporte PWA
- Contratos compartidos entre frontend y backend
- Convenciones de linting, formateo, commit hooks, estructura de proyecto y despliegue
Ninguno de estos problemas es especialmente difícil por separado. La fricción aparece cuando tienen que funcionar todos juntos de forma consistente.
Con IA puedes generar cada pieza muy rápido. Pero sin una base clara puedes terminar con una colección de decisiones razonables de manera individual que, juntas, no forman un sistema coherente.
Eso es exactamente lo que quería evitar.
La idea: estandarizar las partes aburridas
El esqueleto es commodity. Debe ser aburrido, estable y tocarse pocas veces. El valor está en el producto que construyes encima.
Quiero que las nuevas aplicaciones comiencen con las mismas convenciones, el mismo modelo de despliegue, el mismo flujo de autenticación y la misma estructura general.
Esto ayuda a las personas: cambiar entre proyectos e incorporar colaboradores resulta más sencillo.
Pero también ayuda a los agentes de IA. En lugar de pedirle a un agente que invente una arquitectura cada vez, puedo pedirle que extienda una arquitectura que ya existe.
Esa diferencia es mucho más importante de lo que parece.
Arquitectura de un vistazo
La arquitectura base: una SPA React detrás de CloudFront, autenticación con Cognito, API Gateway HTTP API, funciones Lambda ARM64, DynamoDB y despliegues mediante GitHub Actions y AWS CDK.
La infraestructura de la aplicación es intencionadamente sencilla:
- El frontend React/Vite se almacena en un bucket S3 privado y se sirve mediante CloudFront usando Origin Access Control (OAC).
- Los usuarios se autentican con Amazon Cognito y configuran TOTP MFA.
- El frontend llama a una HTTP API de API Gateway utilizando el JWT del usuario.
- Las rutas de la API invocan handlers de AWS Lambda organizados por dominio.
- Las funciones Lambda leen y escriben los datos de la aplicación en DynamoDB.
- AWS CDK define la infraestructura y GitHub Actions utiliza OIDC para desplegar sin almacenar credenciales AWS de larga duración en GitHub.
Qué incluye
El starter proporciona una base probada, funcional y opinionada que puedes desplegar en tu propia cuenta AWS:
| Capa | Qué incluye |
|---|---|
| Auth | Cognito User Pool con TOTP MFA obligatorio, roles (ADMIN/USER) y flujo completo de login, incluyendo cambio y reset de contraseña y configuración de TOTP |
| Backend | Funciones Lambda ARM64 en TypeScript ESM, un handler por dominio, validación con zod y verificación JWT |
| Infra | 4 stacks AWS CDK: storage (DynamoDB), auth (Cognito), API (HTTP API + Lambdas) y frontend (S3 privado + CloudFront con OAC y security headers) |
| Frontend | React 18 + Vite + Tailwind, soporte PWA con prompt de actualización controlado, iOS safe-area y autenticación con Amplify |
| Shared | Workspace @app/shared con tipos, constantes y esquemas zod compartidos entre frontend y backend |
| CI/CD | GitHub Actions con OIDC y flujo lint → test → build → deploy; las ramas distintas de main despliegan a test y main despliega a prod |
| Deploys | Despliegue incremental basado en hashes que omite componentes cuyos inputs versionados no han cambiado |
| Tooling | Makefile, husky, ESLint 9, Prettier, tests y comandos unificados |
No pretende ser la arquitectura final para cualquier producto imaginable. Es la base que quiero tener disponible antes de escribir la primera línea de código específica del producto.
Por qué funciona especialmente bien con agentes de IA
Un codebase predecible y bien estructurado actúa como guardrails: cuanto más consistente es la base, con más fiabilidad puede extenderla un agente de IA.
Algo que he aprendido construyendo aplicaciones con agentes de desarrollo es que cuanto más predecible es el codebase, más útil se vuelve el agente.
El starter no es únicamente boilerplate para desarrolladores. También funciona como un conjunto de guardrails arquitectónicos para la IA.
Hay una gran diferencia entre pedir:
Construye una aplicación serverless en AWS.
y pedir:
Añade esta entidad siguiendo el patrón existente de Item. Reutiliza los esquemas compartidos, las convenciones de los handlers Lambda, los stacks CDK, el cliente API y el modelo de autenticación actuales.
El segundo prompt proporciona restricciones y ejemplos en lugar de un lienzo en blanco.
El repositorio incluye un prompt inicial detallado que puedes utilizar con Kiro o con otro agente de desarrollo. El flujo que propongo es:
- Definir la entidad de negocio y sus relaciones.
- Pedir al agente que proponga primero el modelo DynamoDB y las rutas de la API.
- Revisar ese diseño antes de generar código.
- Implementar un corte vertical completo:
shared → backend → infra → frontend. - Mantener
npm run buildynpm run validateen verde. - Desplegar y verificar la funcionalidad antes de pasar a la siguiente entidad.
Es como prefiero trabajar ahora: tareas pequeñas y acotadas sobre una base predecible.
El dominio de referencia: Items + Shares
El starter incluye un ejemplo completo de Item con:
- Ownership por usuario: cada usuario ve los elementos que posee o que otros usuarios han compartido con él
- Compartición de lectura/escritura entre usuarios
- CRUD completo con validación zod
- Endpoints de gestión de usuarios
- Patrones de autorización reutilizables para otras entidades
Es deliberadamente más útil que una Lambda de hello-world.
El objetivo no es conservar Item para siempre. El objetivo es proporcionar al desarrollador y al agente de IA un corte vertical realista que puedan imitar al construir el dominio real.
Empezar: dos placeholders
Solo hay dos tokens específicos del proyecto que debes sustituir en el repositorio:
1
2
# por ejemplo: mi-aplicacion
# por ejemplo: eu-south-2
Después puedes validar y desplegar el starter:
1
2
3
4
5
6
7
8
9
10
npm install
npm run build
# Necesario una sola vez por cuenta/región AWS
cdk bootstrap aws://<account>/<region>
make deploy ENV=test
make create-admin EMAIL=tu@email.com PASSWORD='Temp.123!' ENV=test
make dev-env
make dev
En ese momento tienes una aplicación desplegada con autenticación, TOTP MFA, endpoints API, CRUD y frontend, sin tener que dedicar días a conectar primero toda la base.
Estructura general
1
2
3
4
5
6
7
8
9
.
├── shared/ # @app/shared — tipos, constantes, esquemas zod
├── frontend/ # React SPA (Vite + Tailwind + PWA + Amplify)
├── backend/ # Lambda handlers por dominio
├── infra/cdk/ # CDK: storage + auth + api + frontend stacks
├── scripts/ # deploy, create-user, set-password, dev-frontend
├── .github/workflows/ # pipeline.yml (lint → test → build → deploy)
├── Makefile # interfaz unificada de comandos
└── tsconfig.base.json # configuración TypeScript compartida
Decisiones de diseño clave
Hay varias decisiones que merece la pena destacar.
- Funciones Lambda ARM64: el starter utiliza ejecución basada en AWS Graviton. Para muchas cargas, ARM64 puede ofrecer una relación precio/rendimiento atractiva sin cambiar el modelo de programación de la aplicación.
- HTTP API en lugar de REST API: la aplicación no necesita el conjunto de funcionalidades más amplio de API Gateway REST API, por lo que HTTP API mantiene la capa de API más sencilla y, en general, más económica.
- S3 privado + CloudFront con OAC: el bucket del frontend nunca es público. CloudFront accede mediante Origin Access Control en lugar del modelo legacy Origin Access Identity.
- OIDC para CI/CD: GitHub no necesita access keys de AWS de larga duración. El workflow obtiene credenciales temporales asumiendo un rol IAM mediante OpenID Connect.
- Deploys incrementales:
scripts/deploy.shgenera fingerprints de los inputs versionados y omite los componentes que no han cambiado. - Monorepo con npm workspaces: tipos y esquemas de validación compartidos viven en un workspace local en lugar de publicarse como una dependencia independiente.
- Dos entornos explícitos: la base utiliza
testyprod. Las ramas distintas demaindespliegan atest;maindespliega aprod.
El repositorio actual utiliza Node.js 22 para Lambda. La versión concreta del runtime es deliberadamente un detalle de implementación y no parte del contrato arquitectónico, de forma que pueda evolucionar sin cambiar el patrón general.
Lo que he dejado fuera deliberadamente
Un starter pierde utilidad cuando intenta resolver todas las arquitecturas posibles.
Este repositorio deliberadamente no intenta incluirlo todo:
- Kubernetes u orquestación de contenedores
- Bases de datos relacionales por defecto
- Descomposición en microservicios
- Workflows event-driven complejos
- Arquitectura multi-región
- Gobierno enterprise multi-account
- Todos los posibles controles de observabilidad, cumplimiento o seguridad
Son requisitos perfectamente válidos cuando el producto los necesita. Simplemente no son una complejidad que quiera pagar por defecto en cada nueva aplicación.
El principio es: empieza con la arquitectura más sencilla que resuelva el problema y añade complejidad cuando exista una razón concreta para hacerlo.
Para quién es
Este starter encaja especialmente bien con:
- Desarrolladores que construyen side projects, prototipos, productos SaaS o herramientas internas en AWS
- Equipos que quieren un punto de partida estandarizado para nuevas aplicaciones serverless
- Desarrolladores que utilizan agentes de IA y quieren que extiendan patrones existentes en lugar de inventar otros nuevos
- Proyectos donde DynamoDB, Lambda, API Gateway, Cognito y una SPA React son un encaje arquitectónico razonable
Cuándo no lo utilizaría
No empezaría con esta plantilla si la aplicación necesita fundamentalmente una arquitectura distinta, por ejemplo:
- Cargas de larga duración o intensivas en cómputo que encajan mejor en contenedores
- Un dominio relacional donde las transacciones SQL y las relaciones sean centrales en el modelo
- Una plataforma enterprise madura que ya imponga otros estándares de CI/CD, identidad, networking o gobierno
- Un sistema cuya arquitectura principal sea asíncrona/event-driven en lugar de request/response
La finalidad de un starter no es eliminar el pensamiento arquitectónico. Es eliminar el setup arquitectónico repetitivo cuando la misma base ya es adecuada para el problema.
Pruébalo
El repositorio es público y tiene licencia MIT:
👉 github.com/alazaroc/serverless-monorepo-aws-starter
La IA ha reducido drásticamente el coste de escribir código. Eso no hace que la arquitectura sea menos importante: hace que unos buenos defaults sean más valiosos.
Mi objetivo con este starter es sencillo: conseguir que la infraestructura y la estructura del proyecto sean lo suficientemente aburridas como para que tanto las personas como los agentes de IA puedan centrarse en la parte que realmente cambia en cada proyecto: el producto.
Si construyes algo con él, tienes sugerencias o encuentras algún patrón que debería formar parte de la base, cuéntamelo en los comentarios.