Integrar inteligencia artificial en una aplicación NestJS deja de ser un extra y se vuelve una expectativa del usuario. Aquí aprendes a conectar el SDK de OpenAI a tu blog para autogenerar el summary y la imagen de portada de cada artículo, usando el patrón modular de NestJS y buenas prácticas de seguridad con variables de entorno.
Cómo conectar OpenAI a una aplicación Node con NestJS
El primer paso es entrar a platform.openai.com, ir al dashboard y generar una API key. Aunque también podrías usar Gemini, Mistral o Llama, el SDK oficial de OpenAI para JavaScript funciona muy bien en Node y agiliza el trabajo frente a llamar la API por request puro.
La key nunca debe vivir suelta en el código. La guardas como variable de entorno y, fuera del proyecto, en un gestor de secretos como Bitwarden, porque si cambias de computadora pierdes el .env. Dentro de Nest, lo correcto es leerla a través del ConfigService para mantener la configuración centralizada.
La instalación es directa con npm install openai. Luego, en el constructor del servicio creas la instancia pasándole la apiKey obtenida del ConfigService. Si la key no existe, lanzas un error temprano para evitar fallos silenciosos en producción [03:45].
¿Qué SDK usar para conectar OpenAI con Node? El paquete oficial openai de npm. Te da tipado, métodos como responses e images, y maneja la autenticación con tu API key.
Por qué crear un módulo de AI siguiendo el patrón modular de NestJS
En lugar de mezclar la lógica de IA con el módulo de posts, creas un módulo independiente llamado AI. Esto sigue el patrón de programación modular que ya usabas con UserModule, AuthModule y PostModule, y te permite agregar mañana servicios para Gemini o Mistral sin tocar el resto del código.
Dentro del módulo AI generas un servicio específico, OpenAIService, usando el CLI de Nest con la bandera --flat para que no cree subcarpetas y especificando que se ubique dentro de ai/services. Así el módulo queda como contenedor de múltiples integraciones de IA, no de una sola [05:30].
Para que otros módulos puedan consumirlo, lo expones en los exports del AIModule y luego lo importas en PostModule a través de imports. Ese es el contrato que permite la inyección de dependencias entre módulos en NestJS.
Cómo generar resúmenes e imágenes con la API de OpenAI
El método generateSummary usa la nueva interfaz responses de OpenAI, lanzada hace pocos meses, que es más declarativa y menos verbose que chat.completions. Le pasas dos cosas: una instruction que define el rol ("eres un asistente que genera resúmenes de blog posts") y un input con el contenido del usuario. La respuesta vive en response.output_text.
Para la imagen, llamas a images con el modelo de DALL·E. Construyes un prompt dinámico tipo "Genera una imagen para un blog post acerca de..." concatenado con el texto, y especificas response_format: 'url' para recibir un enlace donde queda alojada la imagen.
Antes de devolver la URL, validas que data[0].url exista. Si no, lanzas un error porque la generación falló. Ambos métodos retornan una Promise<string>: uno con el resumen, otro con la URL de la imagen [09:15].
¿Qué diferencia hay entre responses y chat.completions en OpenAI? responses es la API más reciente, más declarativa y simple. chat.completions sigue funcionando pero requiere más configuración y estructura de mensajes.
Cómo implementar un endpoint publish que automatice todo el flujo
En el controlador de posts creas un endpoint PUT /posts/:id/publish. No recibe DTO porque la gracia es que sea automático: el usuario solo necesita haber escrito antes un título, contenido y al menos una categoría.
El método publish del servicio hace varias validaciones encadenadas:
- Verifica que el post exista y obtiene también su
user.profile.
- Compara el
user.id del post con el usuario logueado y rechaza la operación si no coinciden.
- Exige que
post.content, post.title y al menos una categoría existan, porque sin eso no hay material para autogenerar nada.
Una vez pasadas las validaciones, llamas a openAIService.generateSummary(post.content) y luego a generateImage(summary). Con esos resultados construyes un updatedPost haciendo merge del original con los nuevos campos: isDraft: false, el summary generado y la coverImage con la URL devuelta por DALL·E. Guardas con save y devuelves el post completo con un findOne final [14:20].
Qué pasa cuando el modelo no respeta el límite de caracteres
Al probar el flujo con un artículo sobre tortugas marinas generado en ChatGPT, apareció un error 500: el resumen excedía los 255 caracteres permitidos en la columna de la base de datos. La primera reacción fue agregar al prompt la instrucción "should generate a summary with 255 characters as limit or less".
No funcionó. Al medir con .length en consola, el resumen tenía 290 caracteres. Esa es una realidad de trabajar con modelos generativos: no siempre cumplen restricciones numéricas exactas, aunque las indiques explícitamente.
La solución pragmática fue bajar el límite del prompt a 100 o 200 caracteres, dándole margen al modelo para excederse sin romper la validación de la base de datos. La alternativa más robusta sería correr una migración para ampliar el tamaño de la columna summary. Tras ajustar el prompt, el endpoint devolvió un resumen válido y una imagen de un océano contaminado con plástico, generada en automático a partir del contenido del artículo [21:40].
¿Por qué OpenAI no respeta el límite de caracteres del prompt? Los modelos generativos optimizan coherencia, no conteo exacto. Para garantizar límites estrictos conviene truncar la respuesta en código o ampliar la columna de la base de datos.
¿Has integrado generación de imágenes con DALL·E en tus propios proyectos? Cuéntame en los comentarios qué prompts te han dado mejores resultados.