Deploy de app Angular en Heroku con Github Actions

17-09-2020 Tiempo de lectura: 10 minutos.

¡¿Qué pasa developers?!

Otro post más, otra tarde más escribiendo para los que me leéis. Esta vez es algo nuevo para mi, nunca he trasteado con Github Actions y hoy quería mostraros un poco lo que en estos días he probado.

¿Qué es Github Actions?

Básicamente es un sistema con el que implementar nuestros flujos en el desarrollo de software, automatizando los despliegues y pruebas con lo que hacer entregas continuas.

Parecía raro que Github aún no tuviera este tipo de sistemas de integración y despliegues continuos, y muchos de vosotros conoceréis y habeis trabajo con otros sistemas de CI/CD como Gitlab CI/CD, Azure Pipeline, Bamboo, Jenkins…

Yo, personalmente, conozco y he trabajado con Gitlab CI/CD y muy contento, la verdad… Así que este post principalmente va a ser para conocer un poco el workflow de los Gitlab Actions y prácticamente lo vamos a hacer al mismo tiempo…

¿Y eso de Heroku?

Heroku es una de las plataformas, creo, que más utilizada o conocida en el mundo del desarrollo, sobre todo, al menos en mi caso, para Pet Projects. En esta plataforma podemos desplegar nuestras aplicaciones en distintos lenguajes, con distintos planes de precio, evidentemente, cuenta con el plan gratis con el que empezar a probar y tener un sitio para los MVP.

Por último Angular Universal y SSR

Angular Universal es la tecnología que nos permitiría ejecutar Angular en el lado del servidor (SSR) con lo que tendremos mejoras de SEO, reducir tiempo de cargas o incluso para mejorar la visibilidad de la web en redes sociales, añadiendo miniaturas. Aunque este no es el objetivo del post, al final de este, veremos como implementarlo.

¡Al lío!

Vamos a utilizar el repositorio que creamos en el post en el que hablamos de testing en NodeJS.

Así que, comenzamos creando el directorio y fichero básico para empezar con Github Actions, esto es un directorio workflows y dentro un fichero workflow.yml. En este directorio podemos tener tantos ficheros como queramos y organizarlo como más nos convenga, por entornos por ejemplo…

En nuestro fichero del workflow que estemos definiendo lo primero que debemos añadir es el nombre (name) del workflow:

name: Hello Github Workflow

Captura de pantalla Github Workflow

Una vez puesto nombre a nuestro flujo de trabajo, podemos indicar en que evento de github se va a ejecutar. Para este ejemplo vamos a utilizar el evento push, pero puedes ver otros eventos que dispararán tus workflows aquí.

on:
    push:
        branches:
            - master

Podríamos indicar todas las ramas que quisiéramos y el workflow se ejecutaría en cada una de las que estuvieran en la lista.

Esto es lo básico que necesitamos para hacer funcionar nuestros despliegues, peeeeeeeero… esto no haría nada, lo que nos falta es añadir y definir los jobs y los pasos de los jobs.

jobs:
  run_tests: # Identificador del job
    name: Run test # Nombre del job
    runs-on: ubuntu-latest # Aquí elegimos el entorno donde se ejecutará. 
    steps: # En este apartado se definen los pasos del job
      - uses: actions/checkout@v1 # Obtenemos una copia de la rama en la que se ejecuta este job
      - name: Install dependency # Nombre del paso
        run: npm install # Comandos que se van a ejecutar en este paso
      - name: Run test
        run: npm test

Ahora ya, sí tendríamos todo lo necesario para hacer funcionar nuestro workflow, el cual tendría un job con dos pasos, en uno instalaremos las dependencias del package.json y en el otro ejecutaremos los tests. Ahora vamos a ver como desplegar esto en Heroku, utilizando variables de entorno para credenciales y tokens de nuestra cuenta en Heroku.

Añadimos otro job con el que conseguiremos desplegar nuestra aplicación:

  build_and_deploy:
    name: Build my app and deploy
    runs-on: ubuntu-latest
    needs: [run_tests]
    steps:
      - uses: actions/checkout@v1
      - uses: akhileshns/heroku-deploy@v3.5.6
        with:
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
          heroku_email: ${{secrets.HEROKU_EMAIL}}

En este bloque, como podéis ver, se ven algunas diferencias respecto al anterior, y vamos a verlas poco a poco.

needs

Con esto bloqueamos este jobs hasta que no termine correctamente el primer jobs. Podríamos concatenar todos los jobs que quisiéramos o nos hicieran falta.

uses: akhileshns/heroku-deploy@v3.5.6

Esta línea nos facilita el despliegue a Heroku ya que es un Github Actions creado para este fin y que podemos encontrar en el marketplace. Podemos encontrar más información en este enlace.

with

Básicamente con with configuramos el Github Action que usamos en el párrafo anterior, yo he puesto lo más básico y los parámetros obligatorios. Creo que por el nombre que tienen no es necesario mucha explicación.

Donde sí me voy a parar es la configuración de las variables de entorno que utilizamos en los workflows, ya que es algo que sí que merece la pena y es muy sencillito.

Como podéis ver estoy utilizando tres variables, y estas variables se configuran en Github, dentro del apartado Settings > Secrets

Captura de pantalla Github Workflow

Podemos definir tantas como queramos y para utilizarlas basta con añadirlas en nuestro fichero YAML de la siguiente forma:

${{secrets.NOMBRE_VARIABLE}}

Básicamente esto es todo lo que hay que hacer y con lo que podemos empezar a funcionar con Github Actions. Y ahora, vamos a crear un proyecto Angular para la siguiente parte del post. La parte del SSR con Angular Universal.

He creado un proyecto nuevo de Angular, está vacío y ahora vamos a instalar la parte de Angular Univeral con el siguiente comando:

ng add @nguniversal/express-engine

El comando que hemos ejecutado nos creará todos esto (info sacada de la documentación oficial):

src/
  index.html                 app web page
  main.ts                    bootstrapper for client app
  main.server.ts             * bootstrapper for server app
  style.css                  styles for the app
  app/ ...                   application code
    app.server.module.ts     * server-side application module
server.ts                    * express web server
tsconfig.json                TypeScript base configuration
tsconfig.app.json            TypeScript browser application configuration
tsconfig.server.json         TypeScript server application configuration
tsconfig.spec.json           TypeScript tests configuration

El comando, aparte de crear la estructura, nos añade unos comandos en el package.json con los que podremos construir nuestros ficheros estáticos:

    "dev:ssr": "ng run example-angular-ssr:serve-ssr",
    "serve:ssr": "node dist/example-angular-ssr/server/main.js",
    "build:ssr": "ng build --prod && ng run example-angular-ssr:server:production",
    "prerender": "ng run example-angular-ssr:prerender"

Podemos probar nuestra aplicación lanzando los comandos que se nos han añadido en el package.json:

npm run build:ssr

y

npm run serve:ssr

Para probar esto del SSR y ver la diferencia podemos lanzar la aplicación Angular como siempre o con los comandos anteriores. He creado un componente sin nada, simplemente creando y utilizando el Routing de Angular podemos acceder a él mediante /albertot. Si hacemos un curl a esta dirección ejecutada como hacemos normalmente ng serve o npm start la respuesta que tenemos es la siguiente imagen de curl con angular, error de que no encuentra la ruta

Da error de ruta no encontrada porque el curl no ejecuta javascript, al igual que pasa con los robots de Google, que no son muy allá ejecutando javascript. No soy experto en SEO, y no se si esta funcionalidad la tienen ya los robots, aún así vamos a ver la diferencia con las misma app lanzada con ssr.

imagen de curl con angular, error de que no encuentra la ruta

En esta segunda imagen se ve como sí que devuelve el HTML del componente que hemos creado. Lo último que vamos a hacer en cuanto a la aplicación Angular es añadir, un título y una imagen cuando se acceda al componente. Esto serviría para que en twitter y whatsapp veamos una imagen, título y descripción.

  ngOnInit(): void {
    this.title.setTitle('Albertot');
    this.meta.addTag({ property: 'og:title', content: 'albertot.dev - Componente de Prueba SSR' }, true);
    this.meta.addTag({ property: "og:image",  content: 'https://albertot.dev/images/ssr_github_angular.png'}, true);
    this.meta.addTag({ property: 'twitter:card', content:'summary_large_image'}, true);
    this.meta.addTag({ property: "twitter:site", content:'@_albertot_'}, true);
    this.meta.addTag({ property: "twitter:creator", content:'@_albertot_'}, true);
    this.meta.addTag({ property: "twitter:title", content:'albertot.dev - Componente de Prueba SSR'}, true);
    this.meta.addTag({ property: "twitter:description", content:'Esto es una prueba para mi nuevo post'}, true);
    this.meta.addTag({ property: "twitter:image", content:'https://albertot.dev/images/ssr_github_angular.png'}, true);
  }

Vamos al deploy…

Nos falta crear un fichero, Procfile, en la raíz de nuestro proyecto que será el que ejecute Heroku una vez despleguemos nuestro código en su plataforma. El contenido de este fichero es muy sencillito:

web: npm run start:heroku

Este comando de npm lo hemos definido en el package.json de la aplicación Angular y es lo que ejecutará Heroku cuando se despliegue.

Este es el fichero del workflow que vamos a usar…

name: Deploy my Angular App
on:
  push:
    branches:
      - master
jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Build angular app
        run: |
          echo "Install dependency"
          npm install
      - name: Deploy
        run: echo "Deploy to heroku"
      - uses: akhileshns/heroku-deploy@v3.5.6
        with:
          heroku_api_key: ${{secrets.HEROKU_API_TOKEN}}
          heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
          heroku_email: ${{secrets.HEROKU_EMAIL}}
        

Para comprobar que todo esto no ha funcionado haremos dos cosas, una es ver que realmente funciona nuestra aplicación Angular entrando en la app desde Heroku o entrando al dominio si lo recordáis.

https://example-angular-ssr.herokuapp.com/

Tendríamos que ver nuestra web solo con el texto Hello Heroku! y si entramos al path albertot

https://example-angular-ssr.herokuapp.com/albertot

Veremos el texto alberto works.

Por otro lado vamos a comprobar lo que hemos implementado de SSR con el título e imágenes en las etiquetas meta.

Entramos en el validador de cards que tiene twitter e introducimos

Captura del validador de cards de twitter

Ya está…tendríamos todo funcionando y desplegado. Como siempre tenéis todo el código en mi repositorio. Podréis ver que me dió problemas con los commits jejeje…

Repositorio del ejemplo

Siento todo este rollazo pero ahí os lo dejo… saludos 🖖