TL;DR
image-uploader es una app full stack que permite subir imágenes (drag & drop o selección manual) y obtener un enlace público para compartirlas. El backend corre en Spring Boot, el frontend en Angular y el almacenamiento usa AWS S3.
El proyecto nació como solución a un challenge de devChallenges.io, pero lo llevé más allá del enunciado: backend propio con Java, almacenamiento cloud real, CI con GitHub Actions y despliegue separado de frontend (Vercel) y backend (Railway).
El origen: un challenge que escalé
El enunciado original de devChallenges pedía una app de subida de imágenes. Muchas soluciones usan almacenamiento local o servicios de terceros con SDKs de frontend. Yo quise hacerlo diferente:
- Backend propio en Spring Boot, no solo un frontend que habla con una API de terceros.
- Almacenamiento real en AWS S3, no en el filesystem del servidor.
- Despliegue completo, con frontend y backend en servicios separados.
La motivación era clara: quería un proyecto que me obligara a resolver problemas de integración cloud, no solo de UI.
Qué hace la aplicación
Funcionalidades
- Subida de imágenes por drag & drop o selección de archivo.
- Soporte de múltiples formatos: PNG, JPG/JPEG, SVG, TIFF, WEBP.
- Límite de 500MB por imagen.
- Loader visual durante la subida.
- Generación de enlace público para compartir la imagen.
- Botón de copiar al portapapeles.
Flujo completo

Decisiones técnicas
Spring Boot + AWS S3
Usar el SDK de AWS para Java desde Spring Boot fue la parte más interesante del backend. Tuve que gestionar:
- Configuración de credenciales AWS (access key, secret, region, bucket).
- Subida de objetos con
PutObjectRequesty metadata de content-type. - Política de acceso del bucket para que las imágenes fueran públicas.
No es un patrón complejo, pero hacerlo por primera vez me obligó a entender cómo funciona S3 por debajo, no solo “subir un archivo”.
Angular como frontend
La experiencia de usuario depende mucho de tres cosas: el drag & drop, el loader y la transición entre estados (vacío, subiendo, completado, error).
Usar Angular me dio estructura para manejar esos estados de forma predecible con RxJS, aunque para una app tan sencilla el framework quizá era overkill. Fue una decisión de aprendizaje, no de eficiencia.
CI con GitHub Actions
El pipeline valida que el proyecto compila correctamente en cada push. No hay tests end-to-end, pero sí compilación y validación básica.
Despliegue separado
- Frontend en Vercel (Angular build estático).
- Backend en Railway (Spring Boot con Docker).
Esta separación me enseñó a manejar CORS, configurar dominios cruzados y entender que el despliegue de una app full stack no es “un solo deploy”.
Lo que aprendí con el proyecto
- Integrar un servicio cloud real cambia la perspectiva. Pasar de “subir a localhost” a “subir a S3” implica entender credenciales, permisos, regiones y costes.
- CORS no es “poner un header y ya”. Entender preflight requests, dominios cruzados y qué headers acepta cada lado me ahorró problemas en proyectos posteriores.
- Un challenge puede ser el punto de partida, no el techo. El enunciado de devChallenges era básico, pero la implementación que elegí me llevó mucho más lejos.
- El despliegue es parte del proyecto, no un paso final. Separar frontend y backend en servicios distintos me enseñó más sobre operación que cualquier tutorial.
Conclusión

image-uploader fue de los primeros proyectos donde conecté frontend, backend y un servicio cloud real de forma autónoma. No es una aplicación compleja, pero el proceso de llevarla desde el enunciado de un challenge hasta un despliegue funcional con S3, Docker, CI y dominios separados fue mucho más formativo de lo que esperaba.
A veces los proyectos aparentemente simples son los que más te enseñan sobre cómo funcionan las cosas fuera de localhost.