Implementar aplicaciones de Node.js en Google App Engine

TL;RD; En este artículo, implementaremos una aplicación Node.js en Google App Engine y, en el proceso, veremos cómo se hace. Esta va a ser una demostración paso a paso...

Introducción

TL;DR; En este artículo, implementaremos una aplicación Node.js en Google App Engine y, en el proceso, veremos cómo se hace.

Esta será una demostración paso a paso desde la configuración de nuestro entorno de Google App Engine hasta la implementación.

NB: Este tutorial requiere una comprensión básica de JavaScript, Node.js, MongoDB, NPM y Express.js.

Puede obtener el código fuente de la aplicación terminada aquí.

¿Qué es Google App Engine?

Google App Engine, una plataforma de computación en la nube (PaaS) lanzada por Google el 7 de abril de 2008, está escrita en C++, PHP, Node.js y Python.

Google App Engine proporciona un entorno ideal para que los desarrolladores y las organizaciones alojen sus aplicaciones sin tener que pensar en la infraestructura, el tiempo de inactividad o el escalamiento a mil millones de usuarios. Google App Engine proporciona todo esto y, en verdad, no necesita preocuparse por el servidor, solo implemente y Google App Engine se encargará de casi todo lo demás. Google App Engine escalará automáticamente y asignará más recursos a su aplicación cuando las solicitudes y la demanda sean enormes.

Google App Engine es un entorno de tiempo de ejecución en la nube que le permite implementar y ejecutar fácilmente aplicaciones web estándar. Proporciona las herramientas para monitorear, escalar y equilibrar la carga de la infraestructura, de modo que pueda concentrarse en crear sus aplicaciones web en lugar de los servidores que las ejecutan.

Crear una instancia de Google App Engine (Instancia de 2.ª generación)

Para comenzar a usar Google App Engine, configuraremos un proyecto de Google Cloud Platform:

  1. Iniciar sesión a su cuenta de Google. Si aún no tiene uno, debe inscribirse.
  2. Vaya al sitio web Motor de aplicaciones
  3. Puede aparecer un cuadro de diálogo solicitando el uso de la versión de la aplicación de Google Cloud Console: "Usar aplicación" o "No ahora". Depende de usted hacer su elección, pero preferiblemente, haga clic en "Ahora no" para continuar.
  4. En la pantalla que aparece, se presentarán dos opciones: "Crear" o "Seleccionar". Para este tutorial, estamos creando un nuevo proyecto, haga clic en el botón "Crear". Si excedió el número máximo de su cuota de proyectos de GCP, debe "Seleccionar" un proyecto.
  5. Escriba el nombre de su proyecto en el campo de texto "Nombre del proyecto", debajo del campo de texto estará su ID de proyecto generado por GCP en función de su nombre de proyecto. haga clic en el botón "Crear" cuando haya terminado.
  6. Después de unos segundos, aparece una pantalla para "Seleccionar una ubicación". En el widget desplegable "Seleccione una región", haga clic en él para seleccionar su región preferida, luego haga clic en "Siguiente".
  7. La siguiente pantalla muestra "Habilitar facturación". Haz clic en "Configurar facturación".
  8. Aparece un diálogo modal, haga clic en "Crear cuenta de facturación".
  9. Escriba su nombre de cuenta de facturación preferido en la siguiente ventana o puede elegir el nombre predeterminado.
  10. Seleccione su país, USD ha sido seleccionado como moneda predeterminada, haga clic en el botón "Confirmar".
  11. En la siguiente ventana, completa tus datos, tanto personales como bancarios
  12. Haga clic en el botón "Enviar y habilitar la facturación". Ahora, hemos creado un proyecto de Google Cloud con la facturación habilitada.

Ahora, hemos terminado!

Instalación de Google Cloud Tools (Cloud SDK)

Las herramientas de Google Cloud son una bolsa llena de utilidades que son todas muy útiles para configurar y acceder a los productos de Google Cloud: Google Kubernetes, Google App Engine, Google Big Query desde su terminal. Para comenzar a instalar el SDK de la nube, vaya a SDK de la nube de Google y descargue el instalador del SDK para su sistema operativo.

Google Cloud SDK contiene herramientas como gcloud y gsutil, pero usaremos la herramienta gcloud para inicializar e implementar nuestra aplicación.

La herramienta gcloud contiene varios comandos para permitir a los usuarios realizar diferentes acciones en un proyecto de Google Cloud:

  • información de gcloud: muestra información sobre su Cloud SDK, su sistema, el usuario que inició sesión y el proyecto actualmente activo.
  • lista de autenticación de gcloud: muestra la lista de cuentas de Google activas en el SDK de Cloud.
  • gcloud init: inicializa un proyecto en la nube de Google.
  • ayuda de gcloud: muestra los comandos disponibles en gcloud y su uso.
  • lista de configuración de gcloud Muestra la lista de las configuraciones de gcloud.

Bien, nos hemos desviado un poco, volvamos a lo que tenemos en la mano, después de descargar el instalador de Cloud SDK, inicie el instalador y siga las indicaciones, asegúrese de verificar las opciones relevantes presentadas. Una vez completada la instalación, el instalador ejecutará el comando gcloud init en una ventana de terminal.

Este comando lo llevará a través de una serie de configuraciones. Se le presentará una opción para iniciar sesión:

1
You must log in to continue. Would you like to log in (Y/n)?

Escribe "Y" y pulsa la tecla Intro. Se iniciará su navegador web predeterminado, donde seleccionará su cuenta de Google preferida. Después de eso, se mostrará en la lista de terminales de sus proyectos de Google:

1
2
3
4
5
6
You are logged in as [YOUR_GOOGLE_ACCOUNT_EMAIL]:

pick cloud project to use:
 [1] [YOUR_PROJECT_NAME]
 [2] Create a new project
Please enter numeric choice or text value (must exactly match list item):

NB: gcloud se seleccionará automáticamente, si solo tiene un proyecto.

A continuación, se le pedirá que elija una zona predeterminada de Compute Engine:

1
2
3
4
5
6
7
Which Google Compute Engine zone would you like to use project default:
 [1] asia-east1-a
 ...
 [16] us-east1-b
 ...
 [25] Do not select default zone
Please enter numeric choice or text value (must exactly match list item):

Después de seleccionar su zona predeterminada, gcloud realiza una serie de comprobaciones e imprime:

1
2
3
4
5
Your project default Compute Engine zone has been set to [YOUR_CHOICE_HERE]
You can change it by running [gcloud config set compute/zone NAME]

Your project default Compute Engine region has been set to [YOUR_CHOICE_HERE]
You can change it by running [gcloud config set compute/region NAME]

¡Su SDK de Google Cloud está configurado y listo para usar!

Configure nuestra aplicación Node.js

Ahora, nuestro proyecto de Google Cloud ha sido configurado. Configuremos nuestra aplicación Node.js. Vamos a crear una API RESTful para la película Black Panther. Guau!!! Esto será genial. El 16 de febrero de 2018, la primera película de superhéroes negros de Marvel se estrenó en los cines de todo el mundo, recaudando $ 903 millones en taquilla, al momento de escribir este artículo, lo que la convierte en la 45.ª película más taquillera de todos los tiempos y la más taquillera. película en 2018.

Construyamos una API que devolverá los personajes de Black Panther.

Punto final de la API

  1. Personaje: este recurso trata sobre los personajes de Black Panther.

    • POST - /blackpanther/ Creates a new Black Panther instance.
    • GET - /blackpanthers/ Returns all Black Panther characters.
    • GET - /blackpanther/<id> Returns the specified Black Panther character id.
    • PUT - /blackpanther/<id> Update a Black Panther character attributes.
    • DELETE - /blackpanther/<id> Delete a Black Panther character.

Estructura del modelo de personaje de Black Panther

1
2
3
4
5
6
7
8
9
{
    "alias": String,
    "occupation": String,
    "gender": String,
    "place_of_birth": String,
    "abilities": String,
    "played_by": String,
    "image_path": String
}

Crear terminales de API para la API de Black Panther

Para comenzar, comencemos creando nuestra carpeta de proyecto, abra su terminal y ejecute el siguiente comando:

1
$ mkdir _nodejs_gae

A continuación, vaya a la carpeta:

1
$ cd _nodejs_gae

La aplicación Node.js se inicializa con el comando npm init. Ahora que estamos dentro de la carpeta de nuestro proyecto, ejecute el siguiente comando para crear una instancia de una aplicación Node.js:

1
$ npm init -y

Este comando crea una aplicación Node.js usando sus credenciales preconfiguradas. A estas alturas, su carpeta se verá así:

1
2
|- _nodejs_gae
    |- package.json

Para seguir las mejores prácticas, vamos a dividir nuestra aplicación en Controladores, Modelos y Rutas. Sí, sé que es excesivo para esta aplicación de demostración, pero siempre es bueno hacerlo bien.

Vamos a crear nuestro archivo index.js (nuestro punto de entrada del servidor) - touch index.js

Cree las siguientes carpetas:

  • rutas mkdir
  • mkdir ctrls
  • modelos mkdir

Ahora tenemos las carpetas routes, ctrls y models.

  • routes: Contendrá todas las rutas definidas en nuestra API y llamará a la función de controlador asignada a la solicitud HTTP coincidente.
  • ctrls: Retendrá la acción para obtener los datos solicitados de los modelos.
  • modelos: Contendrá el Modelo de Base de Datos de nuestra API.

Vamos a tener una ruta, un modelo y un controlador asociado con nuestra API. Ejecute los siguientes comandos para crear los archivos:

  • tocar rutas/ruta.js
  • toca ctrls/ctrl.js
  • modelos táctiles/Character.js

Nuestra estructura de carpetas debería verse así ahora:

1
2
3
4
5
6
7
8
9
|- _nodejs_gae
    |- routes/
        |- route.js
    |- ctrls/
        |- ctrl.js
    |- models/
        |- Character.js
    |- index.js
    |- package.json

Bien, instalemos nuestras dependencias:

  • npm expreso -S
  • npm i mangosta -S
  • npm i analizador de cuerpo -S

Ahora, abrimos nuestro Character.js y pegamos el siguiente código en él:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const mongoose = require('mongoose')

let Character = new mongoose.Schema({
    alias: String,
    occupation: String,
    gender: String,
    place_of_birth: String,
    abilities: String,
    played_by: String,
    image_path: String
})
module.exports = mongoose.model('Character', Character)

Aquí, declaramos nuestro esquema modelo Character usando la clase Schema mongoose. Nuestro modelo fue exportado para que podamos importar y usar el esquema en cualquier lugar de nuestra aplicación.

Bien, agreguemos código a nuestro archivo ctrl.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const Character = require('./../models/Character')

module.exports = {
    getCharacter: (req, res, next) => {
        Character.findById(req.params.id, (err, Character) => {
            if (err)
                res.send(err)
            else if (!Character)
                res.send(404)
            else
                res.send(Character)
            next()
        })
    },
    getAllCharacters: (req, res, next) => {
        Character.find((err, data) => {
            if (err) {
                res.send(err)
            } else {
                res.send(data)
            }
            next()
        })
    },
    deleteCharacter: (req, res, next) => {
        Character.findByIdAndRemove(req.params.id, (err) => {
            if (err)
                res.send(err)
            else
                res.sendStatus(204)
            next()
        })
    },
    addCharacter: (req, res, next) => {
        (new Character(req.body)).save((err, newCharacter) => {
            if (err)
                res.send(err)
            else if (!newCharacter)
                res.send(400)
            else
                res.send(newCharacter)
            next()
        })
    },
    updateCharacter: (req, res, next) => {
        Character.findByIdAndUpdate(req.params.id, req.body, (err, updatedCharacter) => {
            if (err)
                res.send(err)
            else if (!updatedCharacter)
                res.send(400)
            else
                res.send(req.body)
            next()
        })
    }
}

Aquí, declaramos nuestras 4 funciones CRUDy: getCharacter, deleteCharacter, getAllCharaccters y updateCharacter. Como su nombre lo indica, realizan acciones CREATE, READ, UPDATE y DELETE en nuestra API Black Panther.

Bien, abramos el archivo route.js y peguemos el siguiente código en él:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const ctrl = require('./../ctrls/ctrl')

module.exports = (router) => {

    /** get all Black Panther characters */
    router
        .route('/blackpanthers')
        .get(ctrl.getAllCharacters)

    /** save a Black Panther character */
    router
        .route('/blackpanther')
        .post(ctrl.addCharacter)

    /** get a Black Panther character */
    router
        .route('/blackpanther/:id')
        .get(ctrl.getCharacter)

    /** delete a Black Panther character */
    router
        .route('/blackpanther/:id')
        .delete(ctrl.deleteCharacter)

    /** update a Black Panther character */
    router
        .route('/blackpanther/:id')
        .put(ctrl.updateCharacter)
}

Arriba hemos definido dos rutas básicas (/blackpanther y /blackpanther/:id) con diferentes métodos.

Como podemos ver, requerimos el controlador para que cada uno de los métodos de rutas pueda llamar a su respectiva función de controlador.

Finalmente, abrimos nuestro archivo index.js. Aquí, unimos los componentes en uno. Importamos la función de rutas expuesta en routes/route.js y pasamos express.Router() como argumento a nuestra función routes. A continuación, nos conectamos a una instancia de MongoDB y llamamos al método app.listen() para iniciar el servidor.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require('express')
const mongoose = require('mongoose')
const bodyParser = require('body-parser')

const app = express()
const router = express.Router()
const routes = require('./routes/route')

const url = process.env.MONGODB_URI || "mongodb://localhost:27017/blackpanther"

mongoose.connect(url, {
    //useMongoClient: true
})

routes(router)
app.use(bodyParser.json())
app.use('/api/v1', router)

const port = process.env.PORT || 1000

app.listen(port, () => {
    console.log(`Black Panther API v1: ${port}`)
})

Agregue mLab Datastore a nuestros puntos finales de API

Todo este tiempo hemos estado usando una instancia local del almacén de datos de MongoDB. Implementaremos y accederemos a nuestra aplicación en una infraestructura de computación en la nube, por lo que no habrá un almacén de datos local presente. Para conservar nuestros datos, elegiremos una plataforma de datos como servicio (DaaS), mLab.

  • Vaya a mLab
  • Crea una cuenta, si aún no tienes una
  • Vaya a su tablero, cree una nueva base de datos
  • Copie la URL de conexión de la base de datos

Ahora que tenemos nuestra cadena de URL de conexión mLab, modificaremos el archivo index.js:

1
2
3
...
const url = process.env.MONGODB_URI || "mongodb://<DB_USER>:<DB_PASSWORD>@<MLAB_URL>.mlab.com:<MLAB_PORT>/<DB_NAME>"
...

Pruebe nuestra aplicación localmente a través de cURL

Para probar nuestra aplicación en nuestra máquina local. Ejecute el siguiente comando para iniciar el servidor:

1
$ node .

Mostrará algo como esto en su terminal:

1
2
$ node .
Black Panther API v1: 1000

Bien, ahora que nuestra API Black Panther está funcionando, podemos usar cURL para probar las API. Aquí publicaremos en la API para crear un nuevo personaje Black Panther:

1
2
3
4
curl --request POST \
  --url http://localhost:1000/api/v1/blackpanther \
  --header 'content-type: application/json' \
  --data '{"alias":"tchalla","occupation":"King of Wakanda","gender":"male","place_of_birth":"Wakanda","abilities":"enhanced strength","played_by":"Chadwick Boseman"}'

Como tarea para el lector, debe continuar y escribir comandos cURL para otros puntos finales de la API también.

Implemente nuestra aplicación

Ahora, nuestra aplicación nodejs está lista para su implementación, pero antes de hacerlo, hay configuraciones que debemos modificar y agregar. Primero, vamos a crear un archivo app.yaml para nuestro proyecto.

El archivo app.yaml es una configuración de tiempo de ejecución para el entorno de App Engine. app.yaml nos permite configurar nuestro entorno de App Engine (ya sea Node.js, GO, PHP, Ruby, Python, .NET o Java Runtime) antes de la implementación.

Con el archivo app.yaml, podemos hacer lo siguiente:

  • Asignar recursos de red y disco
  • Seleccione el entorno flexible
  • Seleccione la cantidad de núcleos de CPU que se asignarán
  • Especifique el tamaño de memory_gb (RAM)

La lista es larga, puedes ir al recurso [Configurando tu aplicación con app.yaml](https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app- yaml) para ver los ajustes de configuración completos seleccionados por Google.

Bien, vamos a crear el archivo app.yaml en nuestro proyecto:

1
touch app.yaml

Abra el archivo app.yaml y agregue los siguientes contenidos:

1
2
3
4
5
6
7
8
9
runtime: nodejs
env: flex

manual_scaling:
  instances: 1
resources:
  cpu: 1
  memory_gb: 0.5
  disk_size_gb: 10

Mirando la configuración anterior, le estamos diciendo a App Engine que nuestra aplicación se ejecutará en el entorno de tiempo de ejecución de Node.js, también el entorno debe establecerse en flexible.

La ejecución en un entorno flexible genera costos, por lo que redujimos la escala para reducir los costos agregando:

1
2
3
4
5
6
7
...
manual_scaling:
  instances: 1
resources:
  cpu: 1
  memory_gb: 0.5
  disk_size_gb: 10

Aquí, especificamos solo una instancia, un núcleo de CPU, 0,5 G de RAM y un tamaño de disco de 10 G.

Esto es ideal para fines de prueba y no para uso en producción.

A continuación, debemos agregar un inicio en la sección scripts de nuestro package.json, esto lo usa el tiempo de ejecución de Node.js para iniciar nuestra aplicación cuando se implementa.

Sin la propiedad start, el verificador de tiempo de ejecución de Node.js arrojará el error "La detección de la aplicación falló: Error: verificador de nodejs: no se encontró el inicio en la sección de scripts en el paquete.json ni el servidor.js".

Abramos package.json y agreguemos start en la clave scripts:

1
2
3
4
5
6
...
    "scripts": {
        "start": "node .",
        "test": "echo \"Error: no test specified\" && exit 1"
    },
...

Después de esto, ahora estamos listos para implementar. Para implementar nuestra aplicación, ejecute este comando:

1
$ gcloud app deploy

Probando nuestra aplicación implementada con cURL

Para probar nuestra API de aplicación Node.js implementada, necesitaremos usar la URL de destino que Google App Engine nos proporcionó.

1
2
3
4
curl --request POST \
  --url http://YOUR_TARGET_URL.appspot.com/api/v1/blackpanther \
  --header 'content-type: application/json' \
  --data '{"alias":"tchalla","occupation":"King of Wakanda","gender":"male","place_of_birth":"Wakanda","abilities":"enhanced strength","played_by":"Chadwick Boseman"}'

Con cURL, enviamos una solicitud POST y una carga útil del personaje Black Panther a nuestra aplicación Node.js implementada, utilizando la URL de destino como nuestro parámetro de URL.

Nuestro extremo de la API ejecuta la función POST, guarda la carga útil en nuestra base de datos de mLab y nos envía el resultado:

1
2
3
4
5
6
7
8
9
{
    "alias":"tchalla",
    "occupation":"King of Wakanda",
    "gender":"male",
    "place_of_birth":"Wakanda",
    "abilities":"enhanced strength",
    "played_by":"Chadwick Boseman","_id":"5aa3a3905cd0a90010c3e1d9",
    "__v":0
}

¡Felicidades! Implementamos con éxito nuestra primera aplicación Node.js en Google App Engine.

Conclusión

Hemos visto en este artículo lo fácil y sin estrés que Google App Engine nos hace la vida. Además, cómo con solo unos pocos comandos configura un potente motor de tiempo de ejecución e implementa su aplicación en él. No es necesario pensar en la escalabilidad, los recursos, el ancho de banda y el resto.

App Engine piensa por ti.

Para marcar qué ventajas nos ofrece Google App Engine:

  1. Buen informe de errores
  2. Simplifica la seguridad de la API
  3. Confiabilidad y soporte
  4. Cuotas de uso para aplicaciones gratuitas

Por favor, no dude en preguntar si tiene alguna pregunta o comentario en la sección de comentarios.