Ejercicio: Creación de una aplicación web básica

Completado

Hasta ahora, tiene MongoDB y Node.js instalados en la máquina virtual Ubuntu. Ha llegado el momento de crear una aplicación web básica para ver todo en acción. A lo largo del camino, verá cómo encajan AngularJS y Express.

Una excelente manera de aprender es con un ejemplo. La aplicación web que compilará implementa una base de datos de libros básica. La aplicación web le permite mostrar información sobre libros, agregar nuevos libros y eliminar los existentes.

La aplicación web que verá aquí demuestra muchos conceptos que se aplican a la mayoría de las aplicaciones web de la pila MEAN. Según sus necesidades e intereses, puede explorar las características que necesita para compilar sus propias aplicaciones de la pila MEAN.

Este es el aspecto que tendrá la aplicación web de libros.

Screenshot of a web page with a form and submission button.

Así es como encaja cada componente de la pila MEAN.

  • MongoDB almacena información sobre los libros.
  • Express.js enruta cada solicitud HTTP al controlador adecuado.
  • AngularJS conecta la interfaz de usuario con la lógica de negocios del programa.
  • Node.js hospeda la aplicación del lado servidor.

Importante

Con fines de aprendizaje, aquí creará una aplicación web básica. Su objetivo es probar la pila MEAN y ayudarle a hacerse una idea de cómo funciona. La aplicación no es lo suficientemente segura ni está preparada para su uso en producción.

¿Qué ocurre con Express?

Hasta ahora, ha instalado MongoDB y Node.js en la máquina virtual. ¿Qué ocurre con Express.js, la E del acrónimo MEAN?

Express.js es un marco de servidor web que se ha compilado para Node.js y que simplifica el proceso de creación de aplicaciones web.

El objetivo principal de Express es controlar el enrutamiento de solicitudes. Enrutamiento hace referencia a cómo responde la aplicación a una solicitud a un punto de conexión concreto. Los puntos de conexión están formados por una ruta de acceso, o URI, y un método de solicitud, como GET o POST. Por ejemplo, podría responder a una solicitud GET al punto de conexión /book al proporcionar la lista de todos los libros de la base de datos. Podría responder a una solicitud POST al mismo punto de conexión al agregar una entrada a la base de datos en función de los campos que haya rellenado el usuario en un formulario web.

En la aplicación web que compilará en breve, usará Express para enrutar las solicitudes HTTP y devolver el contenido web a su usuario. Express también puede ayudar a las aplicaciones web a trabajar con cookies HTTP y a procesar cadenas de consulta.

Express es un paquete de Node.js. Usará la utilidad npm que viene con Node.js para instalar y administrar paquetes de Node.js. Más adelante en esta unidad, creará un archivo llamado package.json para definir Express y otras dependencias, y después ejecutará el comando npm install para instalar estas dependencias.

¿Qué ocurre con AngularJS?

Como Express, aún no ha instalado AngularJS, la A del acrónimo MEAN.

AngularJS hace que las aplicaciones web sean fáciles de escribir y probar porque le permite separar mejor la apariencia de la página web, el código HTML, de su comportamiento. Si está familiarizado con el patrón Modelo-Vista-Controlador (MVC) o el concepto de enlace de datos, AngularJS le resultará familiar.

AngularJS es lo que denominamos un marco front-end de JavaScript, lo que significa que solo tiene que estar disponible en el cliente que accede a la aplicación. En otras palabras, AngularJS se ejecuta en el explorador web del usuario, no en su servidor web. Y dado que AngularJS es JavaScript, puede usarlo para capturar fácilmente los datos del servidor web para mostrarlos en la página.

En realidad, AngularJS no se instala. En su lugar, agrega una referencia al archivo JavaScript en la página HTML, tal y como se hace con otras bibliotecas de JavaScript. Hay varias maneras de incluir AngularJS en las páginas web. Aquí cargará AngularJS desde una red de entrega de contenido o CDN. Una red CDN es una forma de distribuir imágenes, vídeos y otro contenido de forma geográfica para mejorar las velocidades de descarga.

No agregue este código todavía, pero aquí tiene un ejemplo que carga AngularJS desde una red CDN. Normalmente, agregaría este código a la sección <head> de la página HTML.

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>

Nota:

No confunda AngularJS con Angular. Aunque muchos de los conceptos son similares en ambos, AngularJS es el predecesor de Angular. AngularJS se sigue usando habitualmente para compilar aplicaciones web. Mientras que AngularJS se basa en JavaScript, Angular se basa en TypeScript, un lenguaje de programación que facilita la escritura de programas de JavaScript.

¿Cómo compilo la aplicación?

Aquí seguirá un proceso básico. Primero escribirá el código de la aplicación en Cloud Shell y, luego, usará el protocolo de copia de seguridad (SCP) para copiar los archivos en la máquina virtual. Después, iniciará la aplicación de Node.js y verá los resultados en el explorador.

En la práctica, normalmente escribiría y probaría la aplicación web en un entorno más local, como su portátil o una máquina virtual que ejecute de forma local. Después, podría almacenar el código en un sistema de control de versiones como Git y usar un sistema de integración continua y entrega continua, o CI/CD, como Azure DevOps para probar los cambios y cargarlos en la máquina virtual. Le indicaremos más recursos al final de este módulo.

Creación de la aplicación web de libros

Aquí creará todo el código, los scripts y los archivos HTML que forman la aplicación web. Para mayor brevedad, resaltaremos las partes importantes de cada archivo, pero no entraremos en detalles.

Si aún está conectado a la máquina virtual por medio de SSH, ejecute exit para salir de la sesión de SSH y volver a Cloud Shell.

exit

Ha vuelto a la sesión de Cloud Shell.

Creación de los archivos

  1. En Cloud Shell, ejecute estos comandos para crear las carpetas y los archivos de la aplicación web:

    cd ~
    mkdir Books
    touch Books/server.js
    touch Books/package.json
    mkdir Books/app
    touch Books/app/model.js
    touch Books/app/routes.js
    mkdir Books/public
    touch Books/public/script.js
    touch Books/public/index.html
    

    Esto es lo que incluye:

    • Books es el directorio raíz del proyecto.
      • server.js define el punto de entrada a la aplicación web. Carga los paquetes de Node.js necesarios, especifica el puerto en el que escuchar y empieza a escuchar el tráfico HTTP entrante.
      • package.json proporciona información sobre la aplicación, como su nombre, descripción y qué paquetes de Node.js debe ejecutar la aplicación.
    • Books/app contiene código que se ejecuta en el servidor.
      • model.js define la conexión de base de datos y el esquema. Piense en ella como el modelo de datos de la aplicación.
      • routes.js controla el enrutamiento de solicitudes. Por ejemplo, define las solicitudes GET al punto de conexión /book al proporcionar la lista de todos los libros de la base de datos.
    • Books/public contiene archivos que se entregan directamente en el explorador del cliente.
      • index.html contiene la página de índice. Contiene un formulario web que permite al usuario enviar información sobre los libros. También muestra todos los libros de la base de datos y le permite eliminar las entradas de la base de datos.
      • script.js contiene el código JavaScript que se ejecuta en el explorador del usuario. Puede enviar solicitudes al servidor para mostrar libros, agregar libros a la base de datos y eliminar libros de la base de datos.
  2. Ejecute el comando code para abrir los archivos mediante el editor de Cloud Shell.

    code Books
    

Creación del modelo de datos

  1. En el editor, abra app/model.js y agregue lo siguiente:

    var mongoose = require('mongoose');
    var dbHost = 'mongodb://localhost:27017/Books';
    mongoose.connect(dbHost, { useNewUrlParser: true } );
    mongoose.connection;
    mongoose.set('debug', true);
    var bookSchema = mongoose.Schema( {
        name: String,
        isbn: {type: String, index: true},
        author: String,
        pages: Number
    });
    var Book = mongoose.model('Book', bookSchema);
    module.exports = Book;
    

    Importante

    Cada vez que pegue o cambie código en un archivo en el editor, no olvide guardarlo posteriormente mediante el menú "..." o la tecla de aceleración (CTRL+S en Windows y Linux, o Comando+S en macOS).

    Este código usa Mongoose para simplificar el proceso de transferencia de datos dentro y fuera de MongoDB. Mongoose es un sistema basado en esquemas para modelar datos. El código define un documento de base de datos denominado "Book" (Libro) con el esquema proporcionado. El esquema define cuatro campos que describen un único libro:

    • El nombre o título del libro.
    • Su identificador internacional estándar para libros, o ISBN, que identifica el libro de forma única.
    • Su autor.
    • El número de páginas que contiene.

    Después, creará los controladores HTTP que asignan las solicitudes GET, POST y DELETE a las operaciones de base de datos.

Creación de las rutas de Express.js que controlan las solicitudes HTTP

  1. En el editor, abra app/routes.js y agregue el siguiente código:

    var path = require('path');
    var Book = require('./model');
    var routes = function(app) {
        app.get('/book', function(req, res) {
            Book.find({}, function(err, result) {
                if ( err ) throw err;
                res.json(result);
            });
        });
        app.post('/book', function(req, res) {
            var book = new Book( {
                name:req.body.name,
                isbn:req.body.isbn,
                author:req.body.author,
                pages:req.body.pages
            });
            book.save(function(err, result) {
                if ( err ) throw err;
                res.json( {
                    message:"Successfully added book",
                    book:result
                });
            });
        });
        app.delete("/book/:isbn", function(req, res) {
            Book.findOneAndRemove(req.query, function(err, result) {
                if ( err ) throw err;
                res.json( {
                    message: "Successfully deleted the book",
                    book: result
                });
            });
        });
        app.get('*', function(req, res) {
            res.sendFile(path.join(__dirname + '/public', 'index.html'));
        });
    };
    module.exports = routes;
    

    Este código crea cuatro rutas para la aplicación. Aquí tiene una breve descripción de cada una.

    Verbo HTTP Punto de conexión Descripción
    GET /book Recupera todos los libros de la base de datos.
    POST /book Crea un objeto Book en función de los campos que haya proporcionado el usuario en el formulario web y escribe ese objeto en la base de datos.
    DELETE /book/:isbn Elimina el libro tal y como identifica su ISBN de la base de datos.
    GET * Devuelve la página de índice cuando no coincide ninguna otra ruta.

    Express.js puede suministrar respuestas HTTP directamente en el código de control de ruta, o bien puede suministrar contenido estático desde los archivos. Este código muestra ambas opciones. Las tres primeras rutas devuelven datos JSON para las solicitudes de la API de libros. La cuarta ruta (el caso predeterminado) devuelve el contenido del archivo de índice index.html.

Creación de la aplicación de JavaScript del lado cliente

  1. En el editor, abra public/script.js y agregue este código:

    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope, $http) {
        var getData = function() {
            return $http( {
                method: 'GET',
                url: '/book'
            }).then(function successCallback(response) {
                $scope.books = response.data;
            }, function errorCallback(response) {
                console.log('Error: ' + response);
            });
        };
        getData();
        $scope.del_book = function(book) {
            $http( {
                method: 'DELETE',
                url: '/book/:isbn',
                params: {'isbn': book.isbn}
            }).then(function successCallback(response) {
                console.log(response);
                return getData();
            }, function errorCallback(response) {
                console.log('Error: ' + response);
            });
        };
        $scope.add_book = function() {
            var body = '{ "name": "' + $scope.Name +
            '", "isbn": "' + $scope.Isbn +
            '", "author": "' + $scope.Author +
            '", "pages": "' + $scope.Pages + '" }';
            $http({
                method: 'POST',
                url: '/book',
                data: body
            }).then(function successCallback(response) {
                console.log(response);
                return getData();
            }, function errorCallback(response) {
                console.log('Error: ' + response);
            });
        };
    });
    

    Observe cómo este código define un módulo denominado "myApp" y un controlador denominado "myCtrl". No entraremos en detalles sobre cómo funcionan aquí el módulo y los controladores, pero usará estos nombres en el paso siguiente para enlazar la interfaz de usuario (código HTML) con la lógica de negocios de la aplicación.

    Antes ha creado cuatro rutas que controlan varias operaciones GET, POST y DELETE en el servidor. Este código es similar a esas mismas operaciones, pero desde el lado cliente (el explorador web del usuario).

    La función getData, por ejemplo, envía una solicitud GET al punto de conexión /book. Recuerde que el servidor controla esta solicitud al recuperar información sobre todos los libros de la base de datos y devolver esa información como datos JSON. Observe cómo los datos JSON resultantes se asignan a la variable $scope.books. En el paso siguiente, aprenderá cómo afecta esto a lo que ve el usuario en la página web.

    Este código llama a la función getData cuando se carga la página. Puede examinar las funciones del_book y add_book para hacerse una idea de cómo funcionan. No es necesario que el código del lado cliente coincida con el controlador predeterminado del servidor, ya que el controlador predeterminado devuelve la página de índice y no los datos JSON.

Creación de la interfaz del usuario

  1. En el editor, abra public/index.html y agregue este código:

    <!doctype html>
    <html ng-app="myApp" ng-controller="myCtrl">
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>
        <script src="script.js"></script>
    </head>
    <body>
        <div>
        <table>
            <tr>
            <td>Name:</td>
            <td><input type="text" ng-model="Name"></td>
            </tr>
            <tr>
            <td>Isbn:</td>
            <td><input type="text" ng-model="Isbn"></td>
            </tr>
            <tr>
            <td>Author:</td>
            <td><input type="text" ng-model="Author"></td>
            </tr>
            <tr>
            <td>Pages:</td>
            <td><input type="number" ng-model="Pages"></td>
            </tr>
        </table>
        <button ng-click="add_book()">Add</button>
        </div>
        <hr>
        <div>
        <table>
            <tr>
            <th>Name</th>
            <th>Isbn</th>
            <th>Author</th>
            <th>Pages</th>
            </tr>
            <tr ng-repeat="book in books">
            <td><input type="button" value="Delete" data-ng-click="del_book(book)"></td>
            <td>{{book.name}}</td>
            <td>{{book.isbn}}</td>
            <td>{{book.author}}</td>
            <td>{{book.pages}}</td>
            </tr>
        </table>
        </div>
    </body>
    </html>
    

    Este código crea un formulario HTML básico con cuatro campos para enviar datos de libros y una tabla que muestra todos los libros que están almacenados en la base de datos.

    Aunque se trata de código HTML estándar, puede que no conozca los atributos HTML ng-. Estos atributos HTML conectan el código de AngularJS con la interfaz de usuario. Por ejemplo, al seleccionar Agregar, AngularJS llama a la función add_book, que envía los datos del formulario al servidor.

    Puede examinar el código aquí para hacerse una idea de cómo se relaciona cada uno de los atributos ng- con la lógica de negocios de la aplicación.

Creación del servidor de Express.js que hospedará la aplicación

  1. En el editor, abra server.js y agregue este código:

    var express = require('express');
    var bodyParser = require('body-parser');
    var app = express();
    app.use(express.static(__dirname + '/public'));
    app.use(bodyParser.json());
    require('./app/routes')(app);
    app.set('port', 80);
    app.listen(app.get('port'), function() {
        console.log('Server up: http://localhost:' + app.get('port'));
    });
    

    Este código crea la propia aplicación web. Suministra archivos estáticos desde el directorio public y usa las rutas que ha definido anteriormente para controlar las solicitudes.

Definición de las dependencias y la información del paquete

Recuerde que package.json proporciona información sobre la aplicación, como su nombre, descripción y qué paquetes de Node.js debe ejecutar la aplicación.

  1. En el editor, abra package.json y agregue este código:

    {
      "name": "books",
      "description": "Sample web app that manages book information.",
      "license": "MIT",
      "repository": {
        "type": "git",
        "url": "https://github.com/MicrosoftDocs/mslearn-build-a-web-app-with-mean-on-a-linux-vm"
      },
      "main": "server.js",
      "dependencies": {
        "express": "~4.16",
        "mongoose": "~5.3",
        "body-parser": "~1.18"
      }
    }
    

Verá información o metadatos sobre la aplicación, como su nombre, descripción y licencia.

El campo repository especifica dónde se mantiene el código. Como referencia, más adelante puede revisar el código en GitHub en la URL que se muestra aquí.

El campo main define el punto de entrada de la aplicación. Aquí se proporciona para que lo vea íntegramente, pero no es importante porque no tiene previsto publicar la aplicación como un paquete de Node.js para que otros usuarios lo descarguen y usen.

El campo dependencies es importante. Define los paquetes de Node.js que necesita la aplicación. En resumen, se conectará a la máquina virtual una segunda vez y ejecutará el comando npm install para instalar estos paquetes.

Los paquetes de Node suelen usar el esquema de control de versiones Versionamiento Semántico. El número de versión contiene tres componentes: versión principal, versión secundaria y revisión. La notación de tilde ~ indica a npm que instale la versión de revisión más reciente de las versiones principales y secundarias proporcionadas. Las versiones que ve aquí son las más recientes con las que se ha probado este módulo. En la práctica, puede aumentar la versión con el paso del tiempo conforme actualiza y prueba la aplicación para usar las características más recientes que proporciona cada paquete dependiente.

Copia de los archivos en la máquina virtual

Antes de continuar, asegúrese de que tiene a mano la dirección IP de la máquina virtual. Si no la tiene, ejecute estos comandos en Cloud Shell para recuperarla:

ipaddress=$(az vm show \
  --name MeanStack \
  --resource-group <rgn>[sandbox resource group name]</rgn> \
  --show-details \
  --query [publicIps] \
  --output tsv)
echo $ipaddress
  1. Ya ha terminado de editar los archivos. Asegúrese de guardar los cambios de cada archivo y, después, cierre el editor.

    Para cerrar el editor, seleccione los puntos suspensivos que aparecen en la esquina y elija Cerrar editor.

  2. Ejecute el siguiente comando scp para copiar el contenido del directorio ~/Books de la sesión de Cloud Shell en el mismo nombre de directorio de la máquina virtual.

    scp -r ~/Books azureuser@$ipaddress:~/Books
    

Instalación de los paquetes adicionales de Node

Supongamos que, durante el proceso de desarrollo, ha identificado los paquetes adicionales de Node que quiere usar. Por ejemplo, recuerde que app/model.js comienza con esta línea.

var mongoose = require('mongoose');

Recuerde que la aplicación usa Mongoose para ayudar a transferir datos dentro y fuera de la base de datos de MongoDB.

La aplicación también requiere Express.js y los paquetes de body-parser. body-parser es un complemento que permite que Express trabaje con datos del formulario web que ha enviado el cliente.

Conéctese a la máquina virtual e instale los paquetes que ha especificado en package.json.

  1. Antes de conectarse a la máquina virtual, asegúrese de que tiene a mano la dirección IP de la máquina virtual. Si no lo tiene, ejecute los comandos de Cloud Shell de la sección anterior para recuperarla.

  2. Como ha hecho antes, cree una conexión SSH a la máquina virtual:

    ssh azureuser@$ipaddress
    
  3. Vaya al directorio Books dentro del directorio de inicio:

    cd ~/Books
    
  4. Ejecute npm install para instalar los paquetes dependientes:

    sudo apt install npm -y && npm install
    

Mantenga la conexión SSH abierta para la sección siguiente.

Prueba de la aplicación

Ya está listo para probar su aplicación web de Node.js.

  1. En el directorio ~/Books, ejecute este comando para iniciar la aplicación web:

    sudo nodejs server.js
    

    Este comando inicia la aplicación al escuchar en el puerto 80 las solicitudes HTTP entrantes.

  2. En otra pestaña del explorador, vaya a la dirección IP pública de su máquina virtual.

    Verá la página de índice, que incluye un formulario web.

    Screenshot of the book web page with a form and submission button.

    Intente agregar algunos libros a la base de datos. Cada vez que agrega un libro, la página actualiza la lista completa de libros.

    Screenshot of the book web page with sample data populated.

    Para eliminar un libro de la base de datos, también puede seleccionar Eliminar.