One place for hosting & domains

      Cómo crear y presentar imágenes WebP para acelerar su sitio web


      El autor seleccionó la Apache Software Foundation para recibir una donación como parte del programa Write for DOnations.

      Introducción

      WebP es un formato de imagen abierto desarrollado por Google en 2010 que se basa en el formato de video VP8. Desde entonces, el número de sitios web y aplicaciones móviles que usan el formato WebP ha aumentado a un ritmo rápido. Tanto Google Chrome como Opera admiten el formato WebP de forma nativa, y ya que estos navegadores son responsables de, aproximadamente, el 74 % del tráfico de Internet, los usuarios pueden acceder más rápido a los sitios web si utilizan imágenes WebP. También se planea implementar WebP en Firefox.

      El formato WebP admite la compresión de imágenes con y sin pérdida de datos, incluso animación. Su principal ventaja sobre otros formatos de imagen usados en la web es su tamaño de archivo mucho menor, lo que hace que las páginas web se carguen más rápido y reduce el uso de ancho de banda. Usar imágenes WebP puede provocar aumentos considerables en la velocidad de página. Si su aplicación o sitio web está experimentando problemas de rendimiento o un aumento de tráfico, convertir sus imágenes puede ayudar a optimizar el rendimiento de página.

      En este tutorial, utilizará la herramienta de línea de comandos cwebp para convertir imágenes a formato WebP, creando scripts que examinarán y convertirán imágenes en un directorio específico. Por último, explorará dos formas de presentar imágenes WebP a sus visitantes.

      Requisitos previos

      Trabajar con imágenes WebP no requiere ninguna distribución concreta, pero demostraremos cómo trabajar con software pertinente en Ubuntu 16.04 y CentOS 7. Para seguir este tutorial necesitará lo siguiente:

      • Un servidor configurado con un usuario sudo no root. Para configurar un servidor Ubuntu 16.04, puede seguir nuestra Guía de configuración inicial del servidor Ubuntu 16.04. Si desea usar CentOS, puede configurar un servidor CentOS 7 con nuestro Tutorial de configuración inicial del servidor con CentOS 7.

      • Apache instalado en su servidor. Si está usando Ubuntu, puede seguir el paso uno de Cómo instalar la pila de Linux, Apache, MySQL, PHP (LAMP) en Ubuntu 16.04. Si está usando CentOS, deberá seguir el paso uno de Cómo instalar la pila de Linux, Apache, MySQL, PHP (LAMP) en CentOS 7. Asegúrese de configurar los ajustes de su firewall para que permita tráfico HTTP y HTTPS.

      • mod_rewrite instalado en su servidor. Si está usando Ubuntu, puede seguir nuestra guía Cómo reescribir URL con mod_rewrite para Apache en Ubuntu 16.04. En CentOS7, mod_rewrite está instalado y activado por defecto.

      Paso 1: instalación de cwebp y preparación del directorio de imágenes

      En esta sección, instalaremos el software para convertir imágenes y crearemos un directorio con imágenes como prueba.

      En Ubuntu 16.04, puede instalar cwebp, una herramienta que comprime imágenes al formato .webp al escribir lo siguiente:

      • sudo apt-get update
      • sudo apt-get install webp

      En CentOS 7, escriba lo siguiente:

      • sudo yum install libwebp-tools

      Para crear un nuevo directorio de imágenes llamado webp en el root web de Apache (ubicado por defecto en /var/www/html), escriba lo siguiente:

      • sudo mkdir /var/www/html/webp

      Cambie la propiedad de este directorio a su usuario no root sammy:

      • sudo chown sammy: /var/www/html/webp

      Para probar los comandos, puede descargar imágenes gratuitas JPEG y PNG usando wget. Esta herramienta está instalada por defecto en Ubuntu 16.04; si está usando CentOS 7, puede instalarla escribiendo lo siguiente:

      A continuación, descargue las imágenes de prueba usando los siguientes comandos:

      • wget -c "https://upload.wikimedia.org/wikipedia/commons/2/24/Junonia_orithya-Thekkady-2016-12-03-001.jpg?download" -O /var/www/html/webp/image1.jpg
      • wget -c "https://upload.wikimedia.org/wikipedia/commons/5/54/Mycalesis_junonia-Thekkady.jpg" -O /var/www/html/webp/image2.jpg
      • wget -c "https://cdn.pixabay.com/photo/2017/07/18/15/39/dental-care-2516133_640.png" -O /var/www/html/webp/logo.png

      Nota: estas imágenes están disponibles para su uso y redistribución bajo la licencia Atribución-CompartirIgual y la Certificación de dominio público de Creative Commons.

      La mayor parte de su trabajo en el siguiente paso será en el directorio /var/www/html/webp, que puede mover escribiendo lo siguiente:

      Con las imágenes de prueba en su sitio, y el servidor web de Apache, mod_rewrite, y cwebp instalados, está listo para pasar a convertir imágenes.

      Paso 2: compresión de archivos de imagen con cwebp

      Presentar imágenes .webp a los visitantes de un sitio requiere archivos de imagen en versión .webp. En este paso, convertirá imágenes JPEG y PNG al formato .webp usando cwebp. La sintaxis *general *del comando tiene este aspecto:

      • cwebp image.jpg -o image.webp

      La opción -o especifica la ruta al archivo WebP.

      Ya que aún está en el directorio /var/www/html/webp, puede ejecutar el siguiente comando para convertir image1.jpg a image1.webp e image2.jpg a image2.webp:

      • cwebp -q 100 image1.jpg -o image1.webp
      • cwebp -q 100 image2.jpg -o image2.webp

      Establecer el factor de calidad -q en 100 retiene el 100 % de la calidad de la imagen; si no se especifica, el valor predeterminado es 75.

      A continuación, inspeccione el tamaño de las imágenes JPEG y WebP usando el comando ls. La opción -l mostrará el listado de formato largo, que incluye el tamaño del archivo, y la opción -h se asegurará de que ls imprima tamaños legibles para el ser humano:

      • ls -lh image1.jpg image1.webp image2.jpg image2.webp

      Output

      -rw-r--r-- 1 sammy sammy 7.4M Oct 28 23:36 image1.jpg -rw-r--r-- 1 sammy sammy 3.9M Feb 18 16:46 image1.webp -rw-r--r-- 1 sammy sammy 16M Dec 18 2016 image2.jpg -rw-r--r-- 1 sammy sammy 7.0M Feb 18 16:59 image2.webp

      La salida del comando ls muestra que el tamaño de image1.jpg es 7,4 M, mientras que el tamaño de image1.webp es de 3,9 M. Lo mismo se aplica a image2.jpg (16 M) e image2.webp (7 M). El tamaño de estos archivos es de casi la mitad de su tamaño original.

      Para guardar los datos de las imágenes completos y originales durante la compresión, puede usar la opción -lossless en vez de -q. Esta es la mejor opción para mantener la calidad de las imágenes PNG. Para convertir la imagen PNG descargada del paso 1, escriba lo siguiente:

      • cwebp -lossless logo.png -o https://www.digitalocean.com/logo.webp

      El siguiente comando muestra que el tamaño de la imagen WebP (60 K) sin pérdida es de, aproximadamente, la mitad del tamaño de la imagen PNG original (116 K):

      • ls -lh logo.png https://www.digitalocean.com/logo.webp

      Output

      -rw-r--r-- 1 sammy sammy 116K Jul 18 2017 logo.png -rw-r--r-- 1 sammy sammy 60K Feb 18 16:42 https://www.digitalocean.com/logo.webp

      Las imágenes WebP convertidas en el directorio /var/www/html/webp son, aproximadamente, un 50 % menores que sus equivalentes en JPEG y PNG. En la práctica, las tasas de compresión pueden diferir dependiendo de ciertos factores: la tasa de compresión de la imagen original, el formato de archivo, el tipo de conversión (con o sin pérdida), el porcentaje de calidad y su sistema operativo. A medida que convierte más imágenes, puede ver variaciones en las tasas de conversión relacionadas con estos factores.

      Paso 3: conversión de imágenes JPEG y PNG en un directorio

      Escribir una secuencia de comandos simplificará el proceso de conversión al eliminar el trabajo de la conversión manual. Ahora, escribiremos una secuencia de comandos de conversión que encuentre los archivos JPEG y los convierta a formato WebP con una calidad del 90 %, y que también convierta los archivos PNG a imágenes WebP sin pérdida.

      Usando nano o su editor favorito, cree la secuencia de comandos webp-convert.sh en el directorio principal de su usuario.

      La primera línea de la secuencia de comandos tendrá este aspecto:

      ~/webp-convert.sh

      find $1 -type f -and ( -iname "*.jpg" -o -iname "*.jpeg" )
      

      Esta línea tiene los siguientes componentes:

      • find: este comando buscará archivos en un directorio especificado.
      • $1: este parámetro de posición especifica la ruta del directorio de imágenes, tomada de la línea de comandos. En última instancia, hace que la ubicación del directorio sea menos dependiente de la ubicación de la secuencia de comandos.
      • -type f: esta opción le indica a fin d que busque solo archivos regulares.
      • -iname: esta prueba compara los nombres de archivo con un patrón especificado. La prueba -iname, que no distingue entre mayúsculas y minúsculas, le indica a find que busque cualquier nombre de archivo que termine con .jpg (*.jpg) o .jpeg (*.jpeg).
      • -o: este operador lógico da instrucciones al comando find para que enumere los archivos que coinciden con la primera prueba -iname (-iname “*.jpg”) o la segunda (-iname "*.jpeg").
      • (): los paréntesis alrededor de estas pruebas, junto con el operador -and, garantizan que la primera prueba (por ejemplo, -type f) siempre se ejecute.

      La segunda línea de la secuencia de comandos convertirá las imágenes a WebP usando el parámetro -exec. La sintaxis general de este parámetro es -exec command {} ;. La cadena {} se sustituye con cada archivo que el comando procesa una iteración, mientras que ; le indica a find dónde finaliza el comando:

      ~/webp-convert.sh

      find $1 -type f -and ( -iname "*.jpg" -o -iname "*.jpeg" ) 
      -exec bash -c 'commands' {} ;
      

      En este caso, el parámetro -exec necesitará más de un comando para buscar y convertir imágenes:

      • bash: este comando ejecutará una pequeña secuencia de comandos que creará la versión .webp del archivo si no existe. Esta secuencia de comandos se pasará a bash como una cadena gracias a la opción -c.
      • 'commands': este marcador de posición es la secuencia de comandos que creará versiones .webp de sus archivos.

      La secuencia de comandos dentro de 'commands' hará lo siguiente:

      • Crear una variable webp_path.
      • Probar si la versión .webp del archivo existe o no.
      • Crear el archivo si no existe.

      La secuencia de comandos más pequeña tiene este aspecto:

      ~/webp-convert.sh

      ...
      webp_path=$(sed 's/.[^.]*$/.webp/' <<< "$0");
      if [ ! -f "$webp_path" ]; then 
        cwebp -quiet -q 90 "$0" -o "$webp_path";
      fi;
      

      Los elementos de esta secuencia de comandos más pequeña incluyen los siguientes:

      Con esta secuencia de comandos más pequeña en lugar del marcador de posición 'commands', la secuencia de comandos completa para convertir imágenes JPEG, ahora, tendrá este aspecto:

      ~/webp-convert.sh

      # converting JPEG images
      find $1 -type f -and ( -iname "*.jpg" -o -iname "*.jpeg" ) 
      -exec bash -c '
      webp_path=$(sed 's/.[^.]*$/.webp/' <<< "$0");
      if [ ! -f "$webp_path" ]; then 
        cwebp -quiet -q 90 "$0" -o "$webp_path";
      fi;' {} ;
      

      Para convertir imágenes PNG a WebP, vamos a adoptar el mismo enfoque, con dos diferencias. La primera es que el patrón -iname en el comando find será "*.png". La segunda, que el comando de conversión usará la opción -lossless en vez de la opción de calidad -q.

      La secuencia de comandos completa tendrá este aspecto:

      ~/webp-convert.sh

      #!/bin/bash
      
      # converting JPEG images
      find $1 -type f -and ( -iname "*.jpg" -o -iname "*.jpeg" ) 
      -exec bash -c '
      webp_path=$(sed 's/.[^.]*$/.webp/' <<< "$0");
      if [ ! -f "$webp_path" ]; then 
        cwebp -quiet -q 90 "$0" -o "$webp_path";
      fi;' {} ;
      
      # converting PNG images
      find $1 -type f -and -iname "*.png" 
      -exec bash -c '
      webp_path=$(sed 's/.[^.]*$/.webp/' <<< "$0");
      if [ ! -f "$webp_path" ]; then 
        cwebp -quiet -lossless "$0" -o "$webp_path";
      fi;' {} ;
      

      Guarde el archivo y salga del editor.

      A continuación, vamos a poner la secuencia de comandos webp-convert.sh en práctica usando los archivos del directorio /var/www/html/webp. Asegúrese de que el archivo de la secuencia de comandos sea ejecutable con el siguiente comando:

      • chmod a+x ~/webp-convert.sh

      Ejecute la secuencia de comandos en el directorio de imágenes:

      • ./webp-convert.sh /var/www/html/webp

      ¡No ha pasado nada! Eso es porque ya convertimos estas imágenes en el paso 2. A continuación, la secuencia de comandos webp-convert convertirá las imágenes cuando agreguemos nuevos archivos o eliminemos las versiones .webp. Para ver cómo funciona esto, elimine los archivos .webp que creamos en el paso 2:

      • rm /var/www/html/webp/*.webp

      Tras eliminar todas las imágenes .webp, ejecute la secuencia de comandos de nuevo para asegurarse de que funcione:

      • ./webp-convert.sh /var/www/html/webp

      El comando ls confirmará que la secuencia de comandos convirtió las imágenes correctamente:

      • ls -lh /var/www/html/webp

      Output

      -rw-r--r-- 1 sammy sammy 7.4M Oct 28 23:36 image1.jpg -rw-r--r-- 1 sammy sammy 3.9M Feb 18 16:46 image1.webp -rw-r--r-- 1 sammy sammy 16M Dec 18 2016 image2.jpg -rw-r--r-- 1 sammy sammy 7.0M Feb 18 16:59 image2.webp -rw-r--r-- 1 sammy sammy 116K Jul 18 2017 logo.png -rw-r--r-- 1 sammy sammy 60K Feb 18 16:42 https://www.digitalocean.com/logo.webp

      La secuencia de comandos de este paso es la base para utilizar imágenes WebP en su sitio web, ya que necesitará una versión funcional de todas las imágenes en formato WebP para presentarlas a los visitantes. El siguiente paso cubrirá cómo automatizar la conversión de nuevas imágenes.

      Paso 4: inspección de archivos de imagen en un directorio

      En este paso, crearemos una nueva secuencia de comandos para examinar nuestro directorio de imágenes en busca de cambios y convertir, automáticamente, las imágenes recién creadas.

      Crear una secuencia que examine nuestro directorio de imágenes puede abordar ciertos problemas con la secuencia de comandos webp-convert.sh tal como está escrita. Por ejemplo, esta secuencia de comandos no identificará si cambiamos el nombre a una imagen. Si tuviésemos una imagen llamada foo.jpg, ejecutásemos webp-convert.sh, cambiásemos ese archivo a bar.jpg y ejecutásemos webp-convert.sh de nuevo, tendríamos archivos .webp duplicados (foo.webp y bar.webp). Para resolver este problema, y evitar ejecutar la secuencia de comandos manualmente, añadiremos monitores a otra secuencia de comandos. Los monitores examinan archivos o directorios específicos en busca de cambios y ejecutan comandos en respuesta a ellos.

      El comando inotifywait establecerá monitores en nuestra secuencia de comandos. Este comando es parte del paquete inotify-tools, un conjunto de herramientas de línea de comandos que proporcionan una interfaz sencilla para el subsistema de núcleo de inotify. Para instalarlo en Ubuntu 16.04, escriba lo siguiente:

      • sudo apt-get install inotify-tools

      Con CentOS 7, el paquete inotify-tools está disponible en el repositorio EPEL. Instale el repositorio EPEL y el paquete inotify-tools usando los siguientes comandos:

      • sudo yum install epel-release
      • sudo yum install inotify-tools

      A continuación, cree la secuencia de comandos webp-watchers.sh en el directorio principal de su usuario usando nano:

      La primera línea de la secuencia de comandos tendrá este aspecto:

      ~/webp-watchers.sh

      inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1
      

      Esta línea incluye los siguientes elementos:

      • inotifywait: este comando vigila si se producen cambios en un directorio concreto.
      • -q: esta opción indicará a inotifywait que permanezca en modo silencioso y no produzca muchos resultados.
      • -m: esta opción indicará a inotifywait que se ejecute indefinidamente y no salga tras recibir un único evento.
      • -r: esta opción establecerá monitores de forma recursiva, observando un directorio específico y todos sus subdirectorios.
      • -format: esta opción indicará a inotifywait que monitoree los cambios usando el nombre del evento seguido de la ruta del archivo. Los eventos que queremos monitorear son close_write (que se activa cuando un archivo se crea y se escribe por completo en el disco), moved_from y moved_to (que se activan cuando se mueve un archivo), y delete (que se activa cuando se elimina un archivo).
      • $1: este parámetro de posición contiene la ruta de los archivos cambiados.

      A continuación, vamos a añadir un comando grep para establecer si nuestros archivos son imágenes JPEG o PNG o no. La opción -i indicará a grep que ignore el uso de mayúsculas y minúsculas, -E especificará que grep deberá usar expresiones regulares extendidas, y -line-buffered hará que grep pase las líneas coincidentes a un bucle while:

      ~/webp-watchers.sh

      inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1 | grep -i -E '.(jpe?g|png)$' --line-buffered
      

      A continuación, crearemos un bucle while con el comando read; readprocesará el evento que detectó inotifywait, asignándolo a una variable llamada $operation, y asignará la ruta del archivo procesado a una variable llamada $path:

      ~/webp-watchers.sh

      ...
      | while read operation path; do
        # commands
      done;
      

      Vamos a combinar este bucle con el resto de nuestra secuencia de comandos:

      ~/webp-watchers.sh

      inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1 
      | grep -i -E '.(jpe?g|png)$' --line-buffered 
      | while read operation path; do
        # commands
      done;
      

      Una vez que el bucle while haya comprobado el evento, los comandos dentro del bucle realizarán las siguientes acciones, dependiendo del resultado:

      • Crear un nuevo archivo WebP, si se creó un nuevo archivo de imagen o se movió al directorio de destino.
      • Eliminar el archivo WebP, si el archivo de imagen asociado se eliminó o se movió del directorio de destino.

      Hay tres secciones principales dentro del bucle. Una variable llamada webp_path mantendrá la ruta a la versión .webp de la imagen referente:

      ~/webp-watchers.sh

      ...
      webp_path="$(sed 's/.[^.]*$/.webp/' <<< "$path")";
      

      A continuación, la secuencia de comandos probará qué evento se produjo:

      ~/webp-watchers.sh

      ...
      if [ $operation = "MOVED_FROM" ] || [ $operation = "DELETE" ]; then
        # commands to be executed if the file is moved or deleted
      elif [ $operation = "CLOSE_WRITE,CLOSE" ] || [ $operation = "MOVED_TO" ]; then
        # commands to be executed if a new file is created
      fi;
      

      Si el archivo se movió o eliminó, la secuencia de comandos comprobará si hay una versión .webp. Si es así, la secuencia de comandos la eliminará usando rm:

      ~/webp-watchers.sh

      ...
      if [ -f "$webp_path" ]; then
        $(rm -f "$webp_path");
      fi;
      

      Para los archivos recién creados, la compresión se realizará como sigue:

      • Si el archivo equivalente es una imagen PNG, la secuencia de comandos usará compresión sin pérdida.
      • Si no es así, la secuencia de comandos usará una compresión con pérdida con la opción -quality.

      Vamos a añadir los comando cwebp que harán este trabajo en la secuencia de comandos:

      ~/webp-watchers.sh

      ...
      if [ $(grep -i '.png$' <<< "$path") ]; then
        $(cwebp -quiet -lossless "$path" -o "$webp_path");
      else
        $(cwebp -quiet -q 90 "$path" -o "$webp_path");
      fi;
      

      Completo, el archivo webp-watchers.sh tendrá este aspecto:

      ~/webp-watchers.sh

      #!/bin/bash
      echo "Setting up watches.";
      
      # watch for any created, moved, or deleted image files
      inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1 
      | grep -i -E '.(jpe?g|png)$' --line-buffered 
      | while read operation path; do
        webp_path="$(sed 's/.[^.]*$/.webp/' <<< "$path")";
        if [ $operation = "MOVED_FROM" ] || [ $operation = "DELETE" ]; then # if the file is moved or deleted
          if [ -f "$webp_path" ]; then
            $(rm -f "$webp_path");
          fi;
        elif [ $operation = "CLOSE_WRITE,CLOSE" ] || [ $operation = "MOVED_TO" ]; then  # if new file is created
           if [ $(grep -i '.png$' <<< "$path") ]; then
             $(cwebp -quiet -lossless "$path" -o "$webp_path");
           else
             $(cwebp -quiet -q 90 "$path" -o "$webp_path");
           fi;
        fi;
      done;
      

      Guarde y cierre el archivo. No olvide hacer que sea ejecutable:

      • chmod a+x ~/webp-watchers.sh

      Vamos a ejecutar esta secuencia de comandos en el directorio /var/www/html/webp en segundo plano usando &. También vamos a redirigir el resultado estándar y el error estándar a un ~/output.log, para almacenar el resultado en una ubicación fácilmente disponible:

      • ./webp-watchers.sh /var/www/html/webp > output.log 2>&1 &

      Hasta ahora, convirtió los archivos JPEG y PNG en /var/www/html/webp al formato WebP, y estableció monitores para hacer este trabajo usando la secuencia de comandos webp-watchers.sh. Ahora, es momento de explorar las opciones para presentar imágenes WebP a los visitantes de su sitio web.

      Paso 5: presentación de imágenes WebP a los visitantes usando elementos HTML

      En este paso, explicaremos cómo presentar imágenes WebP con elementos HTML. Ahora, debería haber versiones -webp de cada una de las imágenes de prueba JPEG y PNG en el directorio /var/www/html/webp. Ya podemos presentarlas en los navegadores compatibles usando elementos HTML5 (<picture>) o el módulo mod_rewrite de Apache. Usaremos elementos HTML en este paso.

      El elemento <picture> le permite incluir imágenes directamente en sus páginas web y definir más de una fuente de imagen. Si su navegador es compatible con el formato WebP, descargará la versión .webp del archivo en vez de la original, lo que hará que las páginas web se presenten más rápido. Cabe mencionar que el elemento <picture> se admite bien en los navegadores modernos compatibles con el formato WebP.

      El elemento <picture> es un contenedor con <source> y <image> que apuntan a archivos concretos. Si lo usamos <source> para apuntar a una imagen .webp, el navegador determinará si puede manejarla; de lo contrario, volverá al archivo de imagen especificado en el atributo src del elemento <picture>.

      Vamos a usar el archivo logo.pngde nuestro directorio /var/www/html/webp, que convertimos a https://www.digitalocean.com/logo.webp, como en el ejemplo con <source>. Podemos usar el siguiente código HTML para visualizar https://www.digitalocean.com/logo.webp en cualquier navegador que sea compatible con el formato WebP, y logo.png en cualquier navegador que no sea compatible con WebP o con el elemento<picture>.

      Cree un archivo HTML en /var/www/html/webp/picture.html:

      • nano /var/www/html/webp/picture.html

      Añada el siguiente código a la página web para visualizar https://www.digitalocean.com/logo.webp en los navegadores compatibles usando el elemento<picture>:

      /var/www/html/webp/picture.html

      <picture>
        <source  type="image/webp">
        <img src="https://www.digitalocean.com/logo.png" alt="Site Logo">
      </picture>
      

      Guarde y cierre el archivo.

      Para comprobar que todo funcione correctamente, navegue a http://your_server_ip/webp/picture.html. Debería ver la imagen PNG de prueba.

      Ahora que sabe cómo presentar imágenes .webp directamente desde código HTML, vamos a ver cómo automatizar este proceso usando el módulo mod_rewrite de Apache.

      Paso 6: presentación de imágenes WebP usando mod_rewrite

      Si queremos optimizar la velocidad de nuestro sitio, pero tenemos un gran número de páginas o poco tiempo para editar el código HTML, el módulo mod_rewrite de Apache puede ayudarnos a automatizar el proceso de presentar imágenes .webp en los navegadores compatibles.

      Primero, cree un archivo .htaccess en el directorio /var/www/html/webp usando el siguiente comando:

      • nano /var/www/html/webp/.htaccess

      La directiva ifModule probará si mod_rewrite está disponible; si es así, puede desactivarse usando RewriteEngine On. Añada estas directivas a .htaccess::

      /var/www/html/webp/.htaccess

      <ifModule mod_rewrite.c>
        RewriteEngine On 
        # further directives
      </IfModule>
      

      El servidor web realizará varias pruebas para establecer cuándo presentar las imágenes .webp al usuario. Cuando un navegador realiza una solicitud, incluye un encabezado para indicar al servidor qué es capaz de manejar. En el caso de WebP, el navegador enviará un encabezado Accept que contiene image/webp. Comprobaremos si el navegador envió ese encabezado usando RewriteCond, que especifica el criterio que debería coincidir para realizar RewriteRule:

      /var/www/html/webp/.htaccess

      ...
      RewriteCond %{HTTP_ACCEPT} image/webp
      

      Todo debería filtrarse, excepto las imágenes JPEG y PNG. Usando de nuevo RewriteCond, añada una expresión regular (similar a la que usamos en las secciones anteriores) para que se corresponda con la URI solicitada:

      /var/www/html/webp/.htaccess

      ...
      RewriteCond %{REQUEST_URI}  (?i)(.*)(.jpe?g|.png)$ 
      

      El modificador (?i) hará que la coincidencia no distinga entre mayúsculas y minúsculas.

      Para comprobar si existe la versión .webp del archivo, utilice RewriteCond de nuevo como sigue:

      /var/www/html/webp/.htaccess

      ...
      RewriteCond %{DOCUMENT_ROOT}%1.webp -f
      

      Por último, si se cumplieron todas las condiciones anteriores, RewriteRule redirigirá el archivo JPEG o PNG solicitado a su archivo WebP asociado. Tenga en cuenta que se redirigirá usando el marcador -R, en vez de reescribir la URI. La diferencia entre reescribir y redirigir es que el servidor presentará la URI reescrita sin informar al navegador. Por ejemplo, la URI mostrará que la extensión del archivo es .png, pero, en verdad, será un archivo .webp. Añada RewriteRule al archivo:

      /var/www/html/webp/.htaccess

      ...
      RewriteRule (?i)(.*)(.jpe?g|.png)$ %1.webp [L,T=image/webp,R] 
      

      En este momento, la sección mod_rewrite del archivo .htaccess está completa. Pero, ¿qué sucederá si hay un servidor de caché entre su servidor y el cliente? Podría presentar la versión equivocada al usuario final. Por eso, es recomendable comprobar si mod_headers está habilitado, para enviar el encabezado Vary: Accept. El encabezado Vary indica a los servidores de caché (como los servidores proxy), que el tipo de contenido del documento varía dependiendo de las capacidades del navegador que solicita el documento. Además, la respuesta se generará según el encabezado Accept de la solicitud. Una solicitud con un encabezado Accept diferente puede recibir una respuesta distinta. Este encabezado es importante porque impide que las imágenes WebP en caché se presenten en navegadores no compatibles.

      /var/www/html/webp/.htaccess

      ...
      <IfModule mod_headers.c>
        Header append Vary Accept env=REDIRECT_accept
      </IfModule>
      

      Por último, al final del archivo .htaccess, establezca el tipo MIME de las imágenes .webp en image/webp usando la directiva AddType. Esto presentará las imágenes usando el tipo MIME adecuado:

      /var/www/html/webp/.htaccess

      ...
      AddType image/webp .webp
      

      Esta es la versión final de nuestro archivo .htaccess:

      /var/www/html/webp/.htaccess

      <ifModule mod_rewrite.c>
        RewriteEngine On 
        RewriteCond %{HTTP_ACCEPT} image/webp
        RewriteCond %{REQUEST_URI}  (?i)(.*)(.jpe?g|.png)$ 
        RewriteCond %{DOCUMENT_ROOT}%1.webp -f
        RewriteRule (?i)(.*)(.jpe?g|.png)$ %1.webp [L,T=image/webp,R] 
      </IfModule>
      
      <IfModule mod_headers.c>
        Header append Vary Accept env=REDIRECT_accept
      </IfModule>
      
      AddType image/webp .webp
      

      Nota: puede fusionar este .htaccess con otro archivo .htaccess, de haberlo. Si está usando WordPress, por ejemplo, debería copiar este archivo .htaccess y pegarlo en la parte superior del archivo existente.

      Vamos a poner en práctica lo que hicimos en este paso. Si siguió las instrucciones de los pasos anteriores, debería tener las imágenes logo.png y https://www.digitalocean.com/logo.webpen /var/www/html/webp. Vamos a usar una sencilla etiqueta para incluir logo.png en nuestra página web. Cree un nuevo archivo HTML para probar la configuración:

      • nano /var/www/html/webp/img.html

      Introduzca el siguiente código HTML en el archivo:

      /var/www/html/webp/img.html

      <img src="https://www.digitalocean.com/logo.png" alt="Site Logo">
      

      Guarde y cierre el archivo.

      Cuando visite la página web usando Chrome en http://your_server_ip/webp/img.html, notará que la imagen presentada es la versión .webp (intente abrir la imagen en una nueva pestaña). Si utiliza Firefox, obtendrá una imagen .png automáticamente.

      Conclusión

      En este tutorial, cubrimos técnicas básicas para trabajar con imágenes WebP. Explicamos cómo usar cwebp para convertir archivos, así como dos opciones para presentar estas imágenes a los usuarios: el elemento de HTML5 <picture> y mod_rewrite de Apache.

      Para personalizar las secuencias de comandos de este tutorial, puede consultar algunos de los siguientes recursos:

      • Para obtener más información sobre las características del formato WebP y cómo usar las herramientas de conversión, consulte la documentación de WebP.
      • Para conocer más detalles sobre el uso del elemento<picture>, consulte su documentación en MDN.
      • Para entender plenamente cómo usar mod_rewrite, consulte su documentación.

      Usar el formato WebP para sus imágenes reducirá considerablemente el tamaño de los archivos. Esto puede reducir el uso de ancho de banda y hacer que las páginas se carguen más rápido, sobre todo si su sitio web utiliza muchas imágenes.



      Source link

      Cómo crear una aplicación Node.js con Docker


      Introducción

      La plataforma Docker permite a los desarrolladores empaquetar y ejecutar aplicaciones como contenedores. Un contenedor es un proceso aislado que se ejecuta en un sistema operativo compartido, ofreciendo una alternativa más ligera para máquinas virtuales. Si bien los contenedores no son nuevos, ofrecen beneficios, como aislamiento de procesos y estandarización de entornos, que están creciendo en importancia a medida que más desarrolladores utilizan arquitecturas de aplicaciones distribuidas.

      Cuando se construye y escala una aplicación con Docker, el punto de partida suele ser la creación de una imagen para su aplicación, que, luego, puede ejecutar en un contenedor. La imagen incluye el código, las bibliotecas, los archivos de configuración, las variables del entorno y el tiempo de ejecución de su aplicación. Usar una imagen asegura que el entorno de su contenedor esté estandarizado y que solo contenga lo necesario para crear y ejecutar su aplicación.

      En este tutorial, creará la imagen de una aplicación para un sitio web estático que utiliza el marco de trabajo Express y Bootstrap. Luego, creará un contenedor usando esa imagen y la insertará en Docker Hub para su uso futuro. Por último, extraerá la imagen almacenada de su repositorio de Docker Hub y creará otro contenedor para demostrar cómo puede recrear y escalar su aplicación.

      Requisitos previos

      Para seguir este tutorial, necesitará lo siguiente:

      Paso 1: instalación de las dependencias de su aplicación

      Para crear su imagen, primero, deberá crear los archivos de su aplicación, que, luego, puede copiar a su contenedor. Estos archivos incluirán el contenido estático, el código y las dependencias de su aplicación.

      Primero, cree un directorio para su proyecto en el directorio principal de su usuario no root. Llamaremos al nuestro node_project, pero no es necesario que usted utilice la misma denominación:

      Navegue a este directorio:

      Este será el directorio root del proyecto.

      A continuación, cree un archivo package.json con las dependencias de su proyecto y otra información de identificación. Abra el archivo con nano o su editor favorito:

      Añada la siguiente información sobre el proyecto, incluido su nombre, autor, licencia, punto de entrada y dependencias. Asegúrese de sustituir la información de autor con su propio nombre y sus detalles de contacto:

      ~/node_project/package.json

      {
        "name": "nodejs-image-demo",
        "version": "1.0.0",
        "description": "nodejs image demo",
        "author": "Sammy the Shark <sammy@example.com>",
        "license": "MIT",
        "main": "app.js",
        "keywords": [
          "nodejs",
          "bootstrap",
          "express"
        ],
        "dependencies": {
          "express": "^4.16.4"
        }
      }
      

      Este archivo incluye el nombre, el autor y la licencia del proyecto con los que se comparte. Npm recomienda que el nombre de su proyecto sea corto y descriptivo, así como evitar duplicaciones en el registro de npm. Hemos incluido la licencia MIT en el campo de licencia, lo que permite el uso y la distribución gratuitos del código de la aplicación.

      Además, el archivo especifica lo siguiente:

      • "main": el punto de entrada de la aplicación, app.js. Creará este archivo a continuación.
      • "dependencies": las dependencias del proyecto; en este caso, Express 4.16.4 o superior.

      Aunque este archivo no enumera un repositorio, puede añadir uno al seguir estas pautas sobre la adición de un repositorio a su archivo package.json. Es una buena adición si va a crear versiones de su aplicación.

      Guarde y cierre el archivo cuando haya terminado de realizar cambios.

      Para instalar las dependencias de su proyecto, ejecute el siguiente comando:

      Esto instalará los paquetes que haya incluido en su archivo package.json en el directorio de su proyecto.

      Ahora, podemos proceder a crear los archivos de la aplicación.

      Paso 2: creación de los archivos de la aplicación

      Crearemos una página web que ofrece información a los usuarios sobre los tiburones. Nuestra aplicación tendrá un punto de entrada principal, app.js y un directorio views que incluirá los activos estáticos del proyecto. La página de inicio, index.html, ofrecerá a los usuarios algunos datos preliminares y un enlace a una página con información más detallada sobre tiburones, sharks.html. En el directorio views, crearemos tanto la página de inicio como la página sharks.html.

      Primero, abra el archivo app.js en el directorio principal del proyecto para definir las rutas del proyecto:

      La primera parte del archivo creará la aplicación Express y los objetos Router, y definirá el directorio base y el puerto como constantes:

      ~/node_project/app.js

      const express = require('express');
      const app = express();
      const router = express.Router();
      
      const path = __dirname + '/views/';
      const port = 8080;
      

      La función require carga el módulo express, que, luego, usaremos para crear los objetos app y router. El objeto router realizará la función de enrutamiento de la aplicación, y, al definir las rutas de método HTTP, las añadiremos a este objeto para definir cómo se deben manejar las solicitudes.

      Esta sección del archivo también establece dos constantes: path y port:

      • path: define el directorio base, que será el subdirectorio de views dentro del directorio actual del proyecto.
      • port: le indica a la aplicación que escuche el puerto 8080 y se conecte a él.

      A continuación, establezca las rutas de la aplicación usando el objeto router:

      ~/node_project/app.js

      ...
      
      router.use(function (req,res,next) {
        console.log("https://www.digitalocean.com/" + req.method);
        next();
      });
      
      router.get("https://www.digitalocean.com/", function(req,res){
        res.sendFile(path + 'index.html');
      });
      
      router.get("https://www.digitalocean.com/sharks", function(req,res){
        res.sendFile(path + 'sharks.html');
      });
      

      La función router.use carga una función de middleware que registrará las solicitudes del router y las transmitirá a las rutas de la aplicación. Estas se definen en las funciones siguientes, que especifican que una solicitud GET a la URL del proyecto base debe devolver la página index.html, mientras que una solicitud GET a la ruta /sharks debe devolver sharks.html.

      Por último, monte el middleware router y los activos estáticos de la aplicación e indíquele a esta que escuche por el puerto 8080:

      ~/node_project/app.js

      ...
      
      app.use(express.static(path));
      app.use("https://www.digitalocean.com/", router);
      
      app.listen(port, function () {
        console.log('Example app listening on port 8080!')
      })
      

      El archivo app.js terminado tendrá este aspecto:

      ~/node_project/app.js

      const express = require('express');
      const app = express();
      const router = express.Router();
      
      const path = __dirname + '/views/';
      const port = 8080;
      
      router.use(function (req,res,next) {
        console.log("https://www.digitalocean.com/" + req.method);
        next();
      });
      
      router.get("https://www.digitalocean.com/", function(req,res){
        res.sendFile(path + 'index.html');
      });
      
      router.get("https://www.digitalocean.com/sharks", function(req,res){
        res.sendFile(path + 'sharks.html');
      });
      
      app.use(express.static(path));
      app.use("https://www.digitalocean.com/", router);
      
      app.listen(port, function () {
        console.log('Example app listening on port 8080!')
      })
      

      Guarde y cierre el archivo cuando haya terminado.

      A continuación, añadamos algunos contenidos estáticos a la aplicación. Comience por crear el directorio views:

      Abra el archivo de la página de destino, index.html:

      Añada el siguiente código al archivo, que importará Bootstrap y creará un componente jumbotron con un enlace a la página de información más detallada sharks.html:

      ~/node_project/views/index.html

      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <title>About Sharks</title>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
          <link href="https://www.digitalocean.com/css/styles.css" rel="stylesheet">
          <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
      </head>
      
      <body>
          <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md">
              <div class="container">
                  <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
                  </button> <a class="navbar-brand" href="https://www.digitalocean.com/#">Everything Sharks</a>
                  <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                      <ul class="nav navbar-nav mr-auto">
                          <li class="active nav-item"><a href="https://www.digitalocean.com/" class="nav-link">Home</a>
                          </li>
                          <li class="nav-item"><a href="https://www.digitalocean.com/sharks" class="nav-link">Sharks</a>
                          </li>
                      </ul>
                  </div>
              </div>
          </nav>
          <div class="jumbotron">
              <div class="container">
                  <h1>Want to Learn About Sharks?</h1>
                  <p>Are you ready to learn about sharks?</p>
                  <br>
                  <p><a class="btn btn-primary btn-lg" href="https://www.digitalocean.com/sharks" role="button">Get Shark Info</a>
                  </p>
              </div>
          </div>
          <div class="container">
              <div class="row">
                  <div class="col-lg-6">
                      <h3>Not all sharks are alike</h3>
                      <p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans.
                      </p>
                  </div>
                  <div class="col-lg-6">
                      <h3>Sharks are ancient</h3>
                      <p>There is evidence to suggest that sharks lived up to 400 million years ago.
                      </p>
                  </div>
              </div>
          </div>
      </body>
      
      </html>
      

      Aquí, la barra de navegación de nivel superior permite a los usuarios alternar entre las páginas Home y Sharks. En el subcomponente navbar-nav, estamos usando la clase active de Bootstrap para indicar la página actual al usuario. También especificamos las rutas a nuestras páginas estáticas, que coinciden con las rutas que definimos en app.js:

      ~/node_project/views/index.html

      ...
      <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
         <ul class="nav navbar-nav mr-auto">
            <li class="active nav-item"><a href="https://www.digitalocean.com/" class="nav-link">Home</a>
            </li>
            <li class="nav-item"><a href="https://www.digitalocean.com/sharks" class="nav-link">Sharks</a>
            </li>
         </ul>
      </div>
      ...
      

      Además, creamos un enlace a nuestra página de información de tiburones en el botón de jumbotron:

      ~/node_project/views/index.html

      ...
      <div class="jumbotron">
         <div class="container">
            <h1>Want to Learn About Sharks?</h1>
            <p>Are you ready to learn about sharks?</p>
            <br>
            <p><a class="btn btn-primary btn-lg" href="https://www.digitalocean.com/sharks" role="button">Get Shark Info</a>
            </p>
         </div>
      </div>
      ...
      

      También hay un enlace a una hoja de estilo personalizado en el encabezado:

      ~/node_project/views/index.html

      ...
      <link href="https://www.digitalocean.com/css/styles.css" rel="stylesheet">
      ...
      

      Crearemos esta hoja de estilo al final de este paso.

      Guarde y cierre el archivo cuando haya terminado.

      Con la página de destino de la aplicación establecida, podemos crear nuestra página de información de tiburones, sharks.html, que ofrecerá más información a los usuarios interesados en los tiburones.

      Abra el archivo:

      Añada el siguiente código, que importa Bootstrap y la hoja de estilo personalizado, y ofrece a los usuarios información detallada sobre ciertos tiburones:

      ~/node_project/views/sharks.html

      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <title>About Sharks</title>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
          <link href="https://www.digitalocean.com/css/styles.css" rel="stylesheet">
          <link href="https://fonts.googleapis.com/css?family=Merriweather:400,700" rel="stylesheet" type="text/css">
      </head>
      <nav class="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md">
          <div class="container">
              <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
              </button> <a class="navbar-brand" href="https://www.digitalocean.com/">Everything Sharks</a>
              <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                  <ul class="nav navbar-nav mr-auto">
                      <li class="nav-item"><a href="https://www.digitalocean.com/" class="nav-link">Home</a>
                      </li>
                      <li class="active nav-item"><a href="https://www.digitalocean.com/sharks" class="nav-link">Sharks</a>
                      </li>
                  </ul>
              </div>
          </div>
      </nav>
      <div class="jumbotron text-center">
          <h1>Shark Info</h1>
      </div>
      <div class="container">
          <div class="row">
              <div class="col-lg-6">
                  <p>
                      <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                      </div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
              </div>
              <div class="col-lg-6">
                  <p>
                      <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
              </div>
          </div>
      </div>
      
      </html>
      

      Tenga en cuenta que, en este archivo, volvemos a usar la clase active para indicar la página actual.

      Guarde y cierre el archivo cuando haya terminado.

      Por último, cree la hoja de estilo CSS personalizado a la que se vinculó en index.html y sharks.html creando, primero, una carpeta css en el directorio views:

      Abra la hoja de estilo:

      • nano views/css/styles.css

      Añada el siguiente código, que establecerá el color y la fuente deseados para nuestras páginas:

      ~/node_project/views/css/styles.css

      .navbar {
          margin-bottom: 0;
      }
      
      body {
          background: #020A1B;
          color: #ffffff;
          font-family: 'Merriweather', sans-serif;
      }
      
      h1,
      h2 {
          font-weight: bold;
      }
      
      p {
          font-size: 16px;
          color: #ffffff;
      }
      
      .jumbotron {
          background: #0048CD;
          color: white;
          text-align: center;
      }
      
      .jumbotron p {
          color: white;
          font-size: 26px;
      }
      
      .btn-primary {
          color: #fff;
          text-color: #000000;
          border-color: white;
          margin-bottom: 5px;
      }
      
      img,
      video,
      audio {
          margin-top: 20px;
          max-width: 80%;
      }
      
      div.caption: {
          float: left;
          clear: both;
      }
      

      Además de establecer fuentes y color, este archivo también limita el tamaño de las imágenes al especificar un ancho máximo, max-width, del 80%. Esto impedirá que ocupen más espacio del que quisiéramos en la página.

      Guarde y cierre el archivo cuando haya terminado.

      Con los archivos de la aplicación establecidos y las dependencias del proyecto instaladas, está listo para iniciar la aplicación.

      Si siguió el tutorial de configuración inicial del servidor que se indica en los requisitos previos, tendrá un firewall activo que solo permite tráfico SSH. Para permitir el tráfico al puerto 8080 ejecute lo siguiente:

      Para iniciar la aplicación, asegúrese de estar en el directorio root de su proyecto:

      Inicie la aplicación con node app.js:

      Ingrese la dirección http://your_server_ip:8080 en su navegador. Visualizará la siguiente página de destino:

      Página de destino de la aplicación

      Haga clic en el botón Get Shark Info. Visualizará la siguiente página de información:

      Página de información de tiburones

      Ahora, tiene una aplicación lista y en funcionamiento. Cuando esté listo, salga del servidor al pulsar CTRL+C. Ahora, podemos pasar a crear el Dockerfile que nos permitirá recrear y escalar esta aplicación como deseemos.

      Paso 3: escribir el Dockerfile

      Su Dockerfile especifica qué se incluirá en el contenedor de su aplicación cuando se ejecute. Usar Dockerfile le permite definir el entorno de su contenedor y evitar discrepancias con las dependencias o versiones de tiempo de ejecución.

      Siguiendo estas pautas sobre la creación de contenedores optimizados, haremos que nuestra imagen sea tan eficiente como sea posible al minimizar el número de capas de imagen y restringir la función de la imagen a un único propósito: recrear los archivos y el contenido estático de nuestra aplicación.

      En el directorio root de su proyecto, cree el Dockerfile:

      Las imágenes de Docker se crean usando una sucesión de imágenes en capas que se acumulan unas sobre otras. El primer paso será añadir la imagen base para nuestra aplicación, que formará el punto de partida de la creación de la aplicación.

      Procederemos a utilizar la imagen node:10-alpine, dado que, al momento de redactar este artículo, es la versión LTS de Node.js recomendada. La imagen alpine se deriva del proyecto Alpine Linux, y nos ayudará a mantener el tamaño de las imágenes bajo. Para obtener más información sobre si la imagen alpine es la opción correcta para su proyecto, consulte la discusión completa en la sección de Variantes de imagen de la página de la imagen Node de Docker Hub.

      Añada la siguiente instrucción FROM para configurar la imagen base de la aplicación:

      ~/node_project/Dockerfile

      FROM node:10-alpine
      

      Esta imagen incluye Node.js y npm. Cada Dockerfile debe comenzar con una instrucción FROM.

      De forma predeterminada, la imagen Node de Docker incluye un usuario node no root que puede usar para evitar ejecutar el contenedor de su aplicación como root. Evitar ejecutar contenedores como root y restringir las capacidades del contenedor solo a las necesarias para la ejecución de sus procesos es una práctica de seguridad recomendada. Por lo tanto, utilizaremos el directorio principal del usuario node como directorio de trabajo de nuestra aplicación, y procederemos a configurarlo como nuestro usuario dentro del contenedor. Para obtener más información sobre las mejores prácticas al trabajar con la imagen Node de Docker, consulte esta guía de prácticas recomendadas.

      Para ajustar los permisos en el código de nuestra aplicación en el contenedor, cree el subdirectorio node_modules en /home/node y el directorio app. Crear estos directorios garantizará que tengan los permisos deseados, lo que será importante al crear módulos de nodo locales en el contenedor con npm install. Además de crear estos directorios, estableceremos la propiedad sobre ellos a nuestro usuario node:

      ~/node_project/Dockerfile

      ...
      RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
      

      Para obtener más información sobre la utilidad de consolidar instrucciones RUN, consulte esta discusión sobre cómo gestionar capas de contenedores.

      A continuación, establezca el directorio de trabajo de la aplicación en /home/node/app:

      ~/node_project/Dockerfile

      ...
      WORKDIR /home/node/app
      

      Si no hay un WORKDIR establecido, Docker creará uno por defecto, por lo que es buena idea configurarlo de manera explícita.

      A continuación, copie los archivos package.json y package-lock.json (para npm 5+):

      ~/node_project/Dockerfile

      ...
      COPY package*.json ./
      

      Añadir esta instrucción COPY antes de ejecutar npm install o copiar el código de la aplicación nos permite aprovechar el mecanismo de almacenamiento en caché de Docker. En cada etapa de la creación, Docker comprobará si tiene una capa en caché para esa instrucción en particular. Si cambiamos package.json, esta capa se reconstruye, pero si no lo hacemos, esta instrucción le permitirá a Docker utilizar la capa de imagen existente y omitir la reinstalación de nuestros módulos de nodo.

      Para garantizar que todos los archivos de la aplicación sean propiedad del usuario node no root, incluido el contenido del directorio node_modules, cambie el usuario a node antes de ejecutar npm install:

      ~/node_project/Dockerfile

      ...
      USER node
      

      Tras copiar las dependencias del proyecto y cambiar nuestro usuario, podemos ejecutar npm install:

      ~/node_project/Dockerfile

      ...
      RUN npm install
      

      A continuación, copie el código de su aplicación con los permisos apropiados al directorio de la aplicación en el contenedor:

      ~/node_project/Dockerfile

      ...
      COPY --chown=node:node . .
      

      Esto garantizará que los archivos de la aplicación sean propiedad del usuario node no root.

      Por último, exponga el puerto 8080 en el contenedor e inicie la aplicación:

      ~/node_project/Dockerfile

      ...
      EXPOSE 8080
      
      CMD [ "node", "app.js" ]
      

      EXPOSE no publica el puerto, sino que actúa como una manera de documentar qué puertos del contenedor se publicarán en el tiempo de ejecución. CMD ejecuta el comando para iniciar la aplicación; en este caso, node app.js. Tenga en cuenta que debe haber una sola instrucción CMD en cada Dockerfile. Si incluye más de una, solo la última tendrá efecto.

      Se pueden hacer muchas cosas con Dockerfile. Para obtener una lista completa de instrucciones, consulte la documentación de referencia de Dockerfile de Docker.

      Así es como se ve el Dockerfile completo:

      ~/node_project/Dockerfile

      
      FROM node:10-alpine
      
      RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
      
      WORKDIR /home/node/app
      
      COPY package*.json ./
      
      USER node
      
      RUN npm install
      
      COPY --chown=node:node . .
      
      EXPOSE 8080
      
      CMD [ "node", "app.js" ]
      

      Guarde y cierre el archivo cuando haya terminado de editar.

      Antes de crear la imagen de la aplicación, añadamos un archivo .dockerignore. Con un funcionamiento similar al de un archivo.gitignore, .dockerignore especifica qué archivos y directorios del directorio de su proyecto no deben copiarse a su contenedor.

      Abra el archivo .dockerignore:

      Dentro del archivo, añada sus módulos de nodo locales, registros de npm, Dockerfile y el archivo .dockerignore:

      ~/node_project/.dockerignore

      node_modules
      npm-debug.log
      Dockerfile
      .dockerignore
      

      Si está trabajando con Git, también es conveniente que añada su directorio .git y el archivo .gitignore.

      Guarde y cierre el archivo cuando haya terminado.

      Ahora, está listo para crear la imagen de la aplicación usando el comando docker build. Usar la marca -t con docker build le permitirá etiquetar la imagen con un nombre fácil de recordar. Dado que vamos a insertar la imagen en Docker Hub, incluiremos nuestro nombre de usuario de Docker Hub en la etiqueta. Vamos a etiquetar la imagen como nodejs-image-demo, pero puede utilizar un nombre de su elección. Recuerde reemplazar también your_dockerhub_usernamecon su nombre de usuario de Docker Hub:

      • docker build -t your_dockerhub_username/nodejs-image-demo .

      El . especifica que el contexto de compilación es el directorio actual.

      La compilación de la imagen tomará entre uno y dos minutos. Una vez que se haya completado, verifique sus imágenes:

      Verá la salida siguiente:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 8 seconds ago 73MB node 10-alpine f09e7c96b6de 3 weeks ago 70.7MB

      Ahora, es posible crear un contenedor con esta imagen usandodocker run. Incluiremos tres marcas con este comando:

      • -p: edita el puerto en el contenedor y lo asigna a un puerto en nuestro host. Usaremos el puerto 80 en el host, pero puede modificarlo como considere necesario si tiene otro proceso en ejecución en ese puerto. Para obtener más información sobre cómo funciona, consulte esta discusión en la documentación de Docker sobre ](https://docs.docker.com/v17.09/engine/userguide/networking/default_network/binding/)enlaces de puerto[.
      • -d: ejecuta el contenedor en segundo plano.
      • --name: permite darle al contenedor un nombre fácil de recordar.

      Ejecute el siguiente comando para compilar el contenedor:

      • docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo

      Una vez que su contenedor esté listo y en funcionamiento, puede inspeccionar una lista de sus contenedores en ejecución con docker ps:

      Verá la salida siguiente:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 your_dockerhub_username/nodejs-image-demo "node app.js" 8 seconds ago Up 7 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo

      Con el contenedor en ejecución, ahora, puede visitar su aplicación ingresando la dirección http://your_server_ip en su navegador. Visualizará la página de inicio de su aplicación una vez más:

      Página de destino de la aplicación

      Ahora que creó una imagen para su aplicación, puede insertarla en Docker Hub para su uso futuro.

      Paso 4: utilizar un repositorio para trabajar con imágenes

      Al insertar la imagen de su aplicación en un registro como Docker Hub, la pone a disposición para su uso posterior a medida que construye y escala sus contenedores. Demostraremos cómo funciona esto al insertar la imagen de la aplicación en un repositorio y, luego, usar la imagen para recrear nuestro contenedor.

      El primer paso para insertar la imagen es ingresar a la cuenta de Docker Hub que creó en los requisitos previos:

      • docker login -u your_dockerhub_username

      Cuando se le indique, ingrese la contraseña de la cuenta de Docker Hub. Iniciar sesión de esta manera creará un archivo ~/.docker/config.json en el directorio principal de su usuario con sus credenciales de Docker Hub.

      Ahora, puede insertar la imagen de la aplicación en Docker Hub usando la etiqueta que creó anteriormente, your_dockerhub_username/nodejs-image-demo:

      • docker push your_dockerhub_username/nodejs-image-demo

      Vamos a probar la utilidad del registro de imágenes al destruir el contenedor y la imagen de nuestra aplicación actual y volver a compilarlos con la imagen en nuestro repositorio.

      Primero, enumere sus contenedores en ejecución:

      Verá la salida siguiente:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e50ad27074a7 your_dockerhub_username/nodejs-image-demo "node app.js" 3 minutes ago Up 3 minutes 0.0.0.0:80->8080/tcp nodejs-image-demo

      Mediante CONTAINER ID, enumerada en su salida, detenga la ejecución del contenedor de la aplicación. Asegúrese de sustituir la ID resaltada debajo por su propia CONTAINER ID:

      Enumere todas sus imágenes con la marca -a:

      Visualizará la siguiente salida con el nombre de su imagen,your_dockerhub_username/nodejs-image-demo, junto con la imagen node y las demás imágenes de su compilación:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 7 minutes ago 73MB <none> <none> 2e3267d9ac02 4 minutes ago 72.9MB <none> <none> 8352b41730b9 4 minutes ago 73MB <none> <none> 5d58b92823cb 4 minutes ago 73MB <none> <none> 3f1e35d7062a 4 minutes ago 73MB <none> <none> 02176311e4d0 4 minutes ago 73MB <none> <none> 8e84b33edcda 4 minutes ago 70.7MB <none> <none> 6a5ed70f86f2 4 minutes ago 70.7MB <none> <none> 776b2637d3c1 4 minutes ago 70.7MB node 10-alpine f09e7c96b6de 3 weeks ago 70.7MB

      Elimine el contenedor detenido y todas las imágenes, incluso las no utilizadas o pendientes, con el siguiente comando:

      Escriba y cuando se le indique, en la salida, para confirmar que desea eliminar el contenedor detenido y las imágenes. Tenga en cuenta que esto también eliminará la memoria caché de su compilación.

      Ahora, eliminó tanto la imagen de su aplicación como el contenedor que la ejecutaba. Para obtener más información sobre la eliminación de contenedores, imágenes y volúmenes de Docker, consulte la sección Cómo eliminar imágenes, contenedores y volúmenes de Docker.

      Con todas sus imágenes y contenedores eliminados, ahora, puede extraer la imagen de la aplicación de Docker Hub:

      • docker pull your_dockerhub_username/nodejs-image-demo

      Enumere sus imágenes una vez más:

      Visualizará la imagen de su aplicación:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE your_dockerhub_username/nodejs-image-demo latest 1c723fb2ef12 11 minutes ago 73MB

      Ahora, puede volver a compilar su contenedor usando el comando que se indica en el Paso 3:

      • docker run --name nodejs-image-demo -p 80:8080 -d your_dockerhub_username/nodejs-image-demo

      Enumere sus contenedores en ejecución:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f6bc2f50dff6 your_dockerhub_username/nodejs-image-demo "node app.js" 4 seconds ago Up 3 seconds 0.0.0.0:80->8080/tcp nodejs-image-demo

      Visite http://your_server_ip una vez más para ver su aplicación en ejecución.

      Conclusión

      En este tutorial, creó una aplicación web estática con Express y Bootstrap, así como una imagen de Docker para la aplicación. Utilizó esta imagen para crear un contenedor y la insertó en Docker Hub. Desde allí, pudo destruir su imagen y su contenedor, y recrearlos usando su repositorio de Docker Hub.

      Si le interesa aprender más sobre cómo trabajar con herramientas como Docker Compose y Docker Machine para crear configuraciones de múltiples contenedores, puede consultar las siguientes guías:

      Para obtener sugerencias generales sobre el trabajo con los datos del contenedor, consulte:

      Si le interesan otros temas relacionados con Docker, consulte nuestra biblioteca completa de tutoriales de Docker.



      Source link

      Cómo crear una aplicación web moderna para gestionar la información de clientes con Django y React on Ubuntu 18.04


      El autor seleccionó Open Sourcing Mental Illness Ltd para recibir una donación como parte del programa Write for Donations.

      Introducción

      Las personas utilizan diferentes tipos de dispositivos para conectarse a internet y navegar por la Web. Debido a esto, las aplicaciones deben ser accesibles desde varios lugares. Para los sitios web tradicionales, tener una IU receptiva suele ser suficiente, pero las aplicaciones más complejas suelen requerir el uso de otras técnicas y arquitecturas. Entre ellas se contempla tener aplicaciones back-end y front-end REST independientes que puedan implementarse como aplicaciones web para el cliente, aplicaciones web progresivas (PWA) o aplicaciones móviles nativas.

      Algunas herramientas que puede utilizar al crear aplicaciones más complejas incluyen:

      • React, un marco de trabajo de JavaScript que permite a los desarrolladores crear frontends web y nativos para sus backend de API REST.
      • Django, una estructura web de Python gratuita y de código abierto que sigue el patrón de arquitectura del software de modelo vista controlador (MVC).
      • Django REST framework, un conjunto de herramientas potentes y flexibles para el desarrollo de REST APIs en Django.

      En este tutorial, usted creará una aplicación web moderna con un backend REST API independiente y un frontend utilizando React, Django y Django REST Framework. Al utilizar React con Django, podrá beneficiarse de los últimos avances en el desarrollo de JavaScript y front-end. En lugar de crear una aplicación de Django que utilice un motor de plantillas incorporado, usted utilizará React como biblioteca de IU, aprovechando su enfoque virtual de Modelo de objetos de documentos (DOM), enfoque declarativo y componentes que rápidamente reproduzcan cambios en los datos.

      La aplicación web que usted creará almacena registros sobre clientes en una base de datos, y puede utilizarlo como punto de partida para una aplicación CRM. Cuando haya terminado, podrá crear, leer, actualizar y borrar registros utilizando una interfaz React de estilo Bootstrap 4.

      Requisitos previos

      Para completar este tutorial, necesitará lo siguiente:

      Paso 1: Creación de un entorno virtual de Python e instalación de dependencias

      En este paso, crearemos un entorno virtual e instalaremos las dependencias necesarias para nuestra aplicación, entre ellas, Django, Django REST framework y django-cors-headers.

      Nuestra aplicación utilizará dos servidores de desarrollo distintos para Django y React. Se ejecutarán en diferentes puertos y funcionarán como dos dominios separados. Debido a esto, debemos permitir el intercambio de recursos de origen cruzado (CORS) para enviar solicitudes HTTP desde React a Django sin que el navegador pueda bloquearlos.

      Navegue a su directorio principal y cree un entorno virtual utilizando el módulo venv Python 3:

      • cd ~
      • python3 -m venv ./env

      Active el entorno virtual creado utilizando “source:

      A continuación, instale las dependencias del proyecto con pip. Entre ellas se incluyen:

      • Django: el marco de trabajo web para el proyecto.
      • Django REST framework: Una aplicación externa que desarrolla REST APIs con Django.
      • django-cors-headers: un paquete que habilita CORS.

      Instalar la estructura de Django:

      • pip install django djangorestframework django-cors-headers

      Con las dependencias del proyecto instaladas, puede crear el proyecto Django y el frontend de React.

      Paso 2: Creación del proyecto Django

      En este paso, generaremos el proyecto Django utilizando las siguientes comandos y utilidades:

      • **django-admin startproject project-name**:django-admin es una utilidad de línea de comandos que se utiliza para realizar tareas con Django. El comando startproject crea un nuevo proyecto Django.

      • **python manage.py startapp myapp**: manage.py es un script de utilidad que se añade automáticamente a cada proyecto de Django, y que ejecuta varias tareas administrativas, como crear nuevas aplicaciones, migrar la base de datos y servir de forma local el proyecto de Django. Su comando startapp crea una aplicación de Django dentro del proyecto Django. En Django, el término aplicación describe un paquete de Python que proporciona algunas características en un proyecto.

      Para comenzar, cree el proyecto Django con django-admin startproject. Hemos de nombrar a nuestro proyecto djangoreactproject

      • django-admin startproject djangoreactproject

      Antes de seguir, observemos la estructura del directorio de nuestro proyecto Django utilizando el comando tree.

      Nota: tree es un comando útil para visualizar estructuras de archivos y directorio desde la línea de comandos. Puede instalarlo con el comando que se indica a continuación:

      • sudo apt-get install tree

      Para utilizarlo, use el comando cd en el directorio que desee y escriba tree o proporcione la ruta al punto de partida con tree /home/sammy/sammys-project.

      Navegue a la carpeta djangoreactproject dentro del root de su proyecto y ejecute el comando tree:

      • cd ~/djangoreactproject
      • tree

      Verá lo siguiente:

      Output

      ├── djangoreactproject │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py

      La carpeta ~/djangoreactproject es la root del proyecto. Dentro de esta carpeta, hay varios archivos que serán importantes para su labor:

      • manage.py: el script de utilidad que realiza diversas tareas administrativas.
      • settings.py: el archivo de configuración principal para el proyecto de Django, donde puede modificar la configuración del proyecto. Estos ajustes incluyen variables como INSTALLED_APPS, una lista de strings que designan las aplicaciones habilitadas para su proyecto. La documentación de Django contiene más información sobre los ajustes disponibles.
      • urls.py: este archivo contiene una lista de patrones de URL y vistas relacionadas. Cada patrón traza una conexión entre una URL y la función que se debe solicitar para esa URL. Para más información sobre URL y vistas, consulte nuestro tutorial sobre Cómo crear vistas de Django.

      Nuestro primer paso en el trabajo con el proyecto será configurar los paquetes que instalamos en el paso anterior, incluidos Django REST framework y el paquete Django CORS, al añadirlos a settings.py. Abra el archivo con nano o su editor favorito:

      • nano ~/djangoreactproject/djangoreactproject/settings.py

      Navegue al ajuste INSTALLED_APPS y añada las aplicaciones rest_framework y corsheaders en la parte inferior de la lista:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
          'rest_framework',
          'corsheaders'
      ]
      

      A continuación, añada el middleware corsheaders.middleware.CorsMiddleware desde el paquete CORS instalado previamente al ajuste MIDDLEWARE. Este ajuste es una lista de middlewares, una clase de Python que contiene código que se procesa cada vez que su aplicación web gestiona una solicitud o respuesta:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      
      MIDDLEWARE = [
      ...
      'django.contrib.messages.middleware.MessageMiddleware',
      'django.middleware.clickjacking.XFrameOptionsMiddleware',
      'corsheaders.middleware.CorsMiddleware'
      ]
      

      A continuación, puede habilitar CORS. El ajuste CORS_ORIGIN_ALLOW_ALL especifica si desea habilitar CORS para todos los dominios o no, y CORS_ORIGIN_WHITELIST es una tupla de Python que contiene URL permitidas. En nuestro caso, dado que el servidor de desarrollo de React se ejecutará en http://localhost:3000, añadiremos nuevos ajustes CORS_ORIGIN_ALLOW_ALL = False y CORS_ORIGIN_WHITELIST('localhost:3000',) a nuestro archivo settings.py. Añade estos ajustes en cualquier parte del archivo:

      ~/djangoreactproject/djangoreactproject/settings.py

      
      ...
      CORS_ORIGIN_ALLOW_ALL = False
      
      CORS_ORIGIN_WHITELIST = (
             'localhost:3000',
      )
      ...
      

      Puede encontrar más opciones de configuración en la documentación de django-cors-headers.

      Guarde el archivo y salga del editor cuando haya terminado.

      Aún en el directorio ~/djangoreactproject, cree una nueva aplicación de Django llamada customers:

      • python manage.py startapp customers

      Contendrá los modelos y las vistas para gestionar clientes. Los modelos definen los campos y los comportamientos de nuestros datos de aplicaciones, mientras que las vistas permiten a nuestras aplicaciones gestionar adecuadamente las solicitudes web y devolver las respuestas requeridas.

      A continuación, añada esta aplicación a la lista de aplicaciones instaladas en el archivo settings.py de su proyecto para que Django la reconozca como parte del proyecto. Abra settings.py de nuevo:

      • nano ~/djangoreactproject/djangoreactproject/settings.py

      Añada la aplicación customers:

      ~/djangoreactproject/djangoreactproject/settings.py

      ...
      INSTALLED_APPS = [
          ...
          'rest_framework',
          'corsheaders',
          'customers'
      ]
      ...
      

      A continuación, *migre *la base de datos e inicie el servidor de desarrollo local. Las migraciones son la manera en que Django propaga los cambios que usted realiza a sus modelos en su esquema de base de datos. Estos cambios pueden ser, por ejemplo, añadir un campo o eliminar un modelo. Para obtener más información sobre modelos y migraciones, consulte Cómo crear modelos de Django.

      Migre la base de datos:

      Inicie el servidor de desarrollo local:

      • python manage.py runserver

      El resultado debe ser similar a lo siguiente:

      Output

      Performing system checks... System check identified no issues (0 silenced). October 22, 2018 - 15:14:50 Django version 2.1.2, using settings 'djangoreactproject.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

      Su aplicación web se ejecuta desde http://127.0.0.1:8000. Si se dirige a esta dirección en su navegador web, debería ver la siguiente página:

      Página de demostración de Django

      En este momento, deje la aplicación en ejecución y abra una nueva terminal para seguir desarrollando el proyecto.

      Paso 3: creación del frontend de React

      En esta sección, vamos a crear la aplicación frontend de nuestro proyecto utilizando React.

      React tiene una herramienta oficial que le permite generar proyectos de React de forma rápida sin tener que configurar Webpack directamente. Webpack es un empaquetador de módulos que se utiliza para agrupar recursos web, como código de JavaScript, CSS e imágenes. Normalmente, para poder utilizar Webpack, debe establecer varias opciones de configuración, pero, gracias a la herramienta create-react-app, no tiene que lidiar con Webpack directamente hasta que decida que necesita más control. Para ejecutar create-react-app puede utilizar npx, una herramienta que ejecuta binarios de paquetes npm.

      En su segunda terminal, asegúrese de que estar en el directorio de su proyecto:

      Cree un proyecto de React llamado frontend utilizando create-react-app y npx

      • npx create-react-app frontend

      A continuación, navegue al interior de su aplicación de React e inicie el servidor de desarrollo:

      • cd ~/djangoreactproject/frontend
      • npm start

      Su aplicación se ejecutará desde http://localhost:3000/:

      Página de demostración de React

      Deje el servidor de desarrollo de React en ejecución y abra otra ventana de terminal para proceder.

      Para ver la estructura del directorio de todo el proyecto en este punto, navegue a la carpeta root y ejecute el comando tree de nuevo:

      • cd ~/djangoreactproject
      • tree

      Verá una estructura como esta:

      Output

      ├── customers │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── djangoreactproject │ ├── __init__.py │ ├── __pycache__ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── frontend │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── README.md │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ └── registerServiceWorker.js │ └── yarn.lock └── manage.py

      Nuestra aplicación utilizará Bootstrap 4 para dar forma a la interfaz de React, por lo que lo incluiremos en el archivo frontend/src/App.css que gestiona nuestros ajustes de CSS. Abra el archivo:

      • nano ~/djangoreactproject/frontend/src/App.css

      Añada la importación que se indica a continuación al comienzo del archivo. Puede eliminar el contenido existente del archivo, pero no es necesario hacerlo:

      ~/djangoreactproject/frontend/src/App.css

      @import  'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
      

      Aquí, @import es una instrucción de CSS que se utiliza para importar reglas de estilo de otras hojas de estilo.

      Ahora que hemos creado las aplicaciones back-end y front-end, vamos a crear el modelo de cliente y algunos datos de prueba.

      Paso 4: creación del modelo de cliente y datos iniciales

      Tras crear la aplicación de Django y el frontend de React, nuestro próximo paso será crear el modelo de cliente, que representa la tabla de base de datos que almacenará información sobre los clientes. No necesita nada de SQL, dado que el mapeo objeto-relacional (ORM) de Django se encargará de las operaciones de la base de datos al asignar las clases y variables de Python a tablas y columnas de SQL. De esta manera, el ORM de Django extrae las interacciones de SQL con la base de datos a través de una interfaz de Python.

      Active su entorno virtual de nuevo:

      • cd ~
      • source env/bin/activate

      Diríjase al directorio customers y abra models.py, un archivo de Python que contiene los modelos de su aplicación:

      • cd ~/djangoreactproject/customers/
      • nano models.py

      El archivo incluirá el siguiente contenido:

      ~/djangoreactproject/customers/models.py

      from django.db import models
      # Create your models here.
      

      La API del modelo de cliente ya está importada en el archivo gracias a la instrucción import from django.db import models. Ahora, añadirá la clase Customer, que extiende models.Model. Cada modelo de Django es una clase de Python que extiende django.db.models.Model.

      El modelo Customer tendrá estos campos de base de datos:

      • first_name: el nombre del cliente.
      • last_name: el apellido del cliente.
      • email: la dirección de correo electrónico del cliente.
      • phone: el número de teléfono del cliente.
      • address: la dirección del cliente.
      • description: la descripción del cliente.
      • createdAt: la fecha en que se añade el cliente.

      También añadiremos la función __str__(), que define la manera en que se mostrará el modelo. En nuestro caso, será con el nombre del cliente. Para obtener más información sobre la creación de clases y la definición de objetos, consulte Cómo crear clases y definir objetos en Python 3.

      Añada el código siguiente al archivo:

      ~/djangoreactproject/customers/models.py

      from django.db import models
      
      class Customer(models.Model):
          first_name = models.CharField("First name", max_length=255)
          last_name = models.CharField("Last name", max_length=255)
          email = models.EmailField()
          phone = models.CharField(max_length=20)
          address =  models.TextField(blank=True, null=True)
          description = models.TextField(blank=True, null=True)
          createdAt = models.DateTimeField("Created At", auto_now_add=True)
      
          def __str__(self):
              return self.first_name
      

      A continuación, migre la base de datos para crear las tablas de la base de datos. El comando makemigrations crea los archivos de migración en los que se añadirán los cambios al modelo, y migrate aplica los cambios en los archivos de migraciones a la base de datos.

      Vuelva a navegar a la carpeta root del proyecto:

      Ejecute lo siguiente para crear los archivos de migración:

      • python manage.py makemigrations

      Verá algo similar a esto:

      Output

      customers/migrations/0001_initial.py - Create model Customer

      Aplique estos cambios en la base de datos:

      Verá un resultado que indica que la migración se realizó correctamente:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, customers, sessions Running migrations: Applying customers.0001_initial... OK

      A continuación, utilizará un archivo de migración de datos para crear datos iniciales de clientes. Un archivo de migración de datos es una migración que añade o altera datos en la base de datos. Cree un archivo de migración de datos vacío para la aplicación customers:

      • python manage.py makemigrations --empty --name customers customers

      Visualizará la siguiente confirmación con el nombre de su archivo de migración:

      Output

      Migrations for 'customers': customers/migrations/0002_customers.py

      Tenga en cuenta que el nombre de su archivo de migración es 0002_customers.py.

      A continuación, navegue al interior de la carpeta de migraciones de la aplicación customers:

      • cd ~/djangoreactproject/customers/migrations

      Abra el archivo de migración creado:

      Este es el contenido inicial del archivo:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      from django.db import migrations
      
      class Migration(migrations.Migration):
          dependencies = [
              ('customers', '0001_initial'),
          ]
          operations = [
          ]        
      

      La instrucción import importa la API de migraciones, una API de Django para crear migraciones, desde django.db, un paquete incorporado que contiene clases para trabajar con bases de datos.

      La clase Migration es una clase de Python que describe las operaciones que se ejecutan al migrar bases de datos. Esta clase extiende migrations.Migration y tiene dos listas:

      • dependencies: contiene las migraciones dependientes.
      • operations: contiene las operaciones que se ejecutarán al aplicar la migración.

      A continuación, añada un método para crear datos de clientes de prueba. Añada el método que se indica a continuación antes de la definición de la clase Migration:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      ...
      def create_data(apps, schema_editor):
          Customer = apps.get_model('customers', 'Customer')
          Customer(first_name="Customer 001", last_name="Customer 001", email="customer001@email.com", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()
      
      ...
      

      En este método, estamos tomando la clase Customer de nuestra aplicación customers y creando un cliente de prueba para insertar en la base de datos.

      Para obtener la clase Customer, que permitirá la creación de nuevos clientes, usamos el método get_model() del objeto apps. El objeto apps representa el registro de aplicaciones instaladas y sus modelos de base de datos.

      El objeto apps se pasará del método RunPython() cuando lo usemos para ejecutar create_data(). Añada el método migrations.RunPython() a la lista operations vacía:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      
      ...
          operations = [
              migrations.RunPython(create_data),
          ]  
      

      RunPython() es parte de la API de Migrations que le permite ejecutar código de Python personalizado en una migración. Nuestra lista operations especifica que este método se ejecutará al aplicar la migración.

      Este es el archivo completo:

      ~/djangoreactproject/customers/migrations/0002_customers.py

      from django.db import migrations
      
      def create_data(apps, schema_editor):
          Customer = apps.get_model('customers', 'Customer')
          Customer(first_name="Customer 001", last_name="Customer 001", email="customer001@email.com", phone="00000000", address="Customer 000 Address", description= "Customer 001 description").save()
      
      class Migration(migrations.Migration):
          dependencies = [
              ('customers', '0001_initial'),
          ]
          operations = [
              migrations.RunPython(create_data),
          ]        
      

      Para obtener más información sobre migraciones de datos, consulte la documentación sobre migraciones de datos en Django

      Para migrar su base de datos, primero, vuelva a navegar a la carpeta root de su proyecto:

      Migre su base de datos para crear los datos de prueba:

      Visualizará un resultado que confirma la migración:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, customers, sessions Running migrations: Applying customers.0002_customers... OK

      Para obtener más detalles sobre este proceso, consulte Cómo crear modelos de Django.

      Con el modelo Customer y los datos de prueba creados, podemos pasar a la creación de la API REST.

      Paso 5: creación de la API REST

      En este paso, vamos a crear la API REST utilizando Django REST Framework. Crearemos varias vistas de API diferentes. Una vista de API es una función que gestiona una solicitud o llamada de API, mientras que un punto de final de API es una URL única que representa un punto de contacto con el sistema REST. Por ejemplo, cuando el usuario envía una solicitud de GET a un punto final de API, Django llama a la función o vista de API correspondiente para gestionar la solicitud y devolver los resultados posibles.

      También utilizaremos serializadores. Un serializador de Django REST Framework permite que instancias de modelos complejas y QuerySets se conviertan en formato JSON para el consumo de API. La clase de serialización también puede funcionar en dirección inversa, proporcionando mecanismos para el análisis y la deserialización de datos en QuerySets y modelos de Django.

      Nuestros puntos finales de API incluirán lo siguiente:

      • api/customers: este punto final se utiliza para crear clientes y devuelve conjuntos paginados de clientes.
      • api/clusters/<pk>: este punto final se utiliza para obtener, actualizar y eliminar clientes individuales por clave primaria o id.

      También crearemos URL en el archivo urls.py del proyecto para los puntos finales correspondientes (es decir, api/clusters y api/clusters/<pk>).

      Comencemos por crear la clase de serialización para nuestro modelo Customer.

      Incorporación de la clase de serialización

      Es necesario crear una clase de serialización para nuestro modelo Customer para transformar las instancias de cliente y QuerySets hacia y desde JSON. Para crear la clase de serialización, primero, cree un archivo serializer.py dentro de la aplicación customers:

      • cd ~/djangoreactproject/customers/
      • nano serializers.py

      Añada el código siguiente para importar el modelo Customer y la API de serializadores:

      ~/djangoreactproject/customers/serializers.py

      from rest_framework import serializers
      from .models import Customer
      

      A continuación, cree una clase de serialización que extienda serializers.ModelSerializer y especifique los campos que han de serializarse:

      ~/djangoreactproject/customers/serializers.py

      
      ...
      class CustomerSerializer(serializers.ModelSerializer):
      
          class Meta:
              model = Customer 
              fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
      

      La clase Meta especifica qué campos y modelos se serializarán: pk, first_name, last_name, email, phone, address, description.

      Este es el contenido completo del archivo:

      ~/djangoreactproject/customers/serializers.py

      from rest_framework import serializers
      from .models import Customer
      
      class CustomerSerializer(serializers.ModelSerializer):
      
          class Meta:
              model = Customer 
              fields = ('pk','first_name', 'last_name', 'email', 'phone','address','description')
      

      Ahora que hemos creado nuestra clase de serialización, podemos añadir las vistas de API.

      Incorporación de vistas de API

      En esta sección, crearemos las vistas de API para nuestra aplicación, a las que llamará Django cuando el usuario visite el punto final correspondiente a la función de vista.

      Abra ~/djangoreactproject/customers/views.py:

      • nano ~/djangoreactproject/customers/views.py

      Elimine lo que haya allí y añada las siguientes importaciones:

      ~/djangoreactproject/customers/views.py

      from rest_framework.response import Response
      from rest_framework.decorators import api_view
      from rest_framework import status
      
      from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
      from .models import Customer 
      from .serializers import *
      

      Estamos importando el serializador que creamos, junto con el modelo Customer y las API de Django y Django REST Framework.

      A continuación, añada la vista para el procesamiento de POST y las solicitudes GET HTTP:

      ~/djangoreactproject/customers/views.py

      ...
      
      @api_view(['GET', 'POST'])
      def customers_list(request):
          """
       List  customers, or create a new customer.
       """
          if request.method == 'GET':
              data = []
              nextPage = 1
              previousPage = 1
              customers = Customer.objects.all()
              page = request.GET.get('page', 1)
              paginator = Paginator(customers, 10)
              try:
                  data = paginator.page(page)
              except PageNotAnInteger:
                  data = paginator.page(1)
              except EmptyPage:
                  data = paginator.page(paginator.num_pages)
      
              serializer = CustomerSerializer(data,context={'request': request} ,many=True)
              if data.has_next():
                  nextPage = data.next_page_number()
              if data.has_previous():
                  previousPage = data.previous_page_number()
      
              return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
      
          elif request.method == 'POST':
              serializer = CustomerSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      

      Primero, usamos el decorador @api_view(['GET', 'POST']) para crear una vista de API que pueda aceptar solicitudes GET y POST. Un decorador es una función que toma otra función y la extiende de forma dinámica.

      En el cuerpo del método, usamos la variable request.method para verificar el método HTTP actual y ejecutar la lógica correspondiente dependiendo del tipo de solicitud:

      • Si se trata de una solicitud GET, el método pagina los datos utilizando Django Paginator y devuelve a la primera página de datos después de la serialización, el conteo de clientes disponibles, el número de páginas disponibles y los enlaces a las páginas anteriores y siguientes. Paginator es una clase integrada de Django que pagina una lista de datos en páginas y proporciona métodos para acceder a los elementos de cada una de ellas.
      • Si se trata de una solicitud POST, el método serializa los datos de clientes recibidos y, luego, llama al método save() del objeto serializador. A continuación, devuelve un objeto de Response, una instancia de HttpResponse, con el código de estado 201. Cada vista que crea se encarga de regresar un objeto HttpResponse. El método save() guarda los datos serializados en la base de datos.

      Para obtener más información sobre HttpResponse y las vistas, consulte esta discusión con respecto a la creación de funciones de vista.

      Ahora, añada la vista de API que se encargará del procesamiento de las solicitudes GET, PUT y DELETE para obtener, actualizar y eliminar clientes por pk (clave primaria):

      ~/djangoreactproject/customers/views.py

      
      ...
      @api_view(['GET', 'PUT', 'DELETE'])
      def customers_detail(request, pk):
       """
       Retrieve, update or delete a customer by id/pk.
       """
          try:
              customer = Customer.objects.get(pk=pk)
          except Customer.DoesNotExist:
              return Response(status=status.HTTP_404_NOT_FOUND)
      
          if request.method == 'GET':
              serializer = CustomerSerializer(customer,context={'request': request})
              return Response(serializer.data)
      
          elif request.method == 'PUT':
              serializer = CustomerSerializer(customer, data=request.data,context={'request': request})
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          elif request.method == 'DELETE':
              customer.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      

      El método está representado con @api_view(['GET', 'PUT', 'DELETE']) para indicar que se trata de una vista de API que puede aceptar las solicitudes GET, PUT y DELETE.

      La marca de revisión en el campo request.method verifica el método de solicitud y, dependiendo de su valor, llama a la lógica correcta:

      • Si se trata de una solicitud GET, se serializan los datos de clientes y se envían utilizando un objeto Response.
      • Si se trata de una solicitud PUT, el método crea un serializador para nuevos datos de cliente y, luego, llama al método save() del objeto serializador creado. Por último, envía un objeto Response con el cliente actualizado.
      • Si se trata de una solicitud DELETE, el método llama al método delete() del objeto de cliente para eliminarlo y, a continuación, devuelve un objeto Response sin datos.

      El archivo completo se ve de este modo:

      ~/djangoreactproject/customers/views.py

      from rest_framework.response import Response
      from rest_framework.decorators import api_view
      from rest_framework import status
      
      from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
      from .models import Customer 
      from .serializers import *
      
      
      @api_view(['GET', 'POST'])
      def customers_list(request):
          """
       List  customers, or create a new customer.
       """
          if request.method == 'GET':
              data = []
              nextPage = 1
              previousPage = 1
              customers = Customer.objects.all()
              page = request.GET.get('page', 1)
              paginator = Paginator(customers, 5)
              try:
                  data = paginator.page(page)
              except PageNotAnInteger:
                  data = paginator.page(1)
              except EmptyPage:
                  data = paginator.page(paginator.num_pages)
      
              serializer = CustomerSerializer(data,context={'request': request} ,many=True)
              if data.has_next():
                  nextPage = data.next_page_number()
              if data.has_previous():
                  previousPage = data.previous_page_number()
      
              return Response({'data': serializer.data , 'count': paginator.count, 'numpages' : paginator.num_pages, 'nextlink': '/api/customers/?page=' + str(nextPage), 'prevlink': '/api/customers/?page=' + str(previousPage)})
      
          elif request.method == 'POST':
              serializer = CustomerSerializer(data=request.data)
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data, status=status.HTTP_201_CREATED)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
      @api_view(['GET', 'PUT', 'DELETE'])
      def customers_detail(request, pk):
          """
       Retrieve, update or delete a customer by id/pk.
       """
          try:
              customer = Customer.objects.get(pk=pk)
          except Customer.DoesNotExist:
              return Response(status=status.HTTP_404_NOT_FOUND)
      
          if request.method == 'GET':
              serializer = CustomerSerializer(customer,context={'request': request})
              return Response(serializer.data)
      
          elif request.method == 'PUT':
              serializer = CustomerSerializer(customer, data=request.data,context={'request': request})
              if serializer.is_valid():
                  serializer.save()
                  return Response(serializer.data)
              return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
      
          elif request.method == 'DELETE':
              customer.delete()
              return Response(status=status.HTTP_204_NO_CONTENT)
      

      Ahora, podemos pasar a crear nuestros puntos finales.

      Incorporación de puntos finales de API

      Ahora, crearemos los puntos finales de API: api/customers/ para consultar y crear clientes, y api/customers/ <pk> para obtener, actualizar o eliminar clientes individuales por su pk.

      Abra ~/djangoreactproject/djangoreactproject/urls.py:

      • nano ~/djangoreactproject/djangoreactproject/urls.py

      Deje lo que haya allí, pero añada la importación a las vistas customers en la parte superior del archivo:

      ~/djangoreactproject/djangoreactproject/urls.py

      from django.contrib import admin
      from django.urls import path
      from customers import views
      from django.conf.urls import url
      

      A continuación, añada las URL de api/customers/ y api/customers/<pk> a la lista urlpatterns que contiene las URL de la aplicación:

      ~/djangoreactproject/djangoreactproject/urls.py

      ...
      
      urlpatterns = [
          path('admin/', admin.site.urls),
          url(r'^api/customers/$', views.customers_list),
          url(r'^api/customers/(?P<pk>[0-9]+)$', views.customers_detail),
      ]
      

      Con nuestros puntos finales REST creados, veamos cómo podemos consumirlos.

      Paso 6: consumo de API REST con Axios.

      En este paso, instalaremos Axios, el cliente HTTP que utilizaremos para realizar llamadas de API. También crearemos una clase para consumir los puntos finales de API que hemos creado.

      Primero, desactive su entorno virtual:

      A continuación, navegue a su carpeta frontend:

      • cd ~/djangoreactproject/frontend

      Instale axios desde npm utilizando:

      La opción --save añade la dependencia axios al archivo package.json de su aplicación.

      A continuación, cree un archivo de JavaScript denominado CustomersService.js, que contendrá el código para llamar a las API REST. Realizaremos esto dentro de la carpeta src, donde residirá el código de la aplicación de nuestro proyecto:

      • cd src
      • nano CustomersService.js

      Añada el código siguiente, que contiene métodos para conectarse a la API REST de Django:

      ~/djangoreactproject/frontend/src/CustomersService.js

      import axios from 'axios';
      const API_URL = 'http://localhost:8000';
      
      export default class CustomersService{
      
          constructor(){}
      
      
          getCustomers() {
              const url = `${API_URL}/api/customers/`;
              return axios.get(url).then(response => response.data);
          }  
          getCustomersByURL(link){
              const url = `${API_URL}${link}`;
              return axios.get(url).then(response => response.data);
          }
          getCustomer(pk) {
              const url = `${API_URL}/api/customers/${pk}`;
              return axios.get(url).then(response => response.data);
          }
          deleteCustomer(customer){
              const url = `${API_URL}/api/customers/${customer.pk}`;
              return axios.delete(url);
          }
          createCustomer(customer){
              const url = `${API_URL}/api/customers/`;
              return axios.post(url,customer);
          }
          updateCustomer(customer){
              const url = `${API_URL}/api/customers/${customer.pk}`;
              return axios.put(url,customer);
          }
      }
      

      La clase CustomersService llamará a los siguientes métodos de Axios:

      • getCustomers(): obtiene la primera página de clientes.
      • getCustomersByURL(): obtiene clientes por URL. Esto permite obtener las siguientes páginas de clientes al pasar enlaces como /api/customers/?page=2.
      • get Customer(): obtiene un cliente por clave primaria.
      • createCustomer(): crea un cliente.
      • updateCustomer(): actualiza un cliente.
      • deleteCustomer(): elimina un cliente.

      Ahora, podemos mostrar los datos de nuestra API en nuestra IU de React al crear un componente CustomersList.

      Paso 7: visualización de datos de la API en la aplicación de React

      En este paso, crearemos el componente CustomersList de React. Un componente de React representa una parte de la IU; también le permite dividir la IU en piezas independientes y reutilizables.

      Comience por crear CustomersList.js en frontend/src:

      • nano ~/djangoreactproject/frontend/src/CustomersList.js

      Inicie la importación de React y Component para crear un componente de React:

      ~/djangoreactproject/frontend/src/CustomersList.js

      import  React, { Component } from  'react';
      

      A continuación, importe e instancie el módulo CustomersService que creó en el paso anterior, el cual proporciona métodos que interactúan con el backend de API REST:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      

      A continuación, cree un componente CustomersList que extienda Component para llamar a la API REST. Un componente de React debería extender o crear una subclase de la clase Component. Para obtener más información acerca de las clases E6 y herencia, consulte nuestro tutorial sobre Comprensión de las clases de JavaScript.

      Añada el siguiente código para crear un componente de React que extienda react.Component:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      class  CustomersList  extends  Component {
      
          constructor(props) {
              super(props);
              this.state  = {
                  customers: [],
                  nextPageURL:  ''
              };
              this.nextPage  =  this.nextPage.bind(this);
              this.handleDelete  =  this.handleDelete.bind(this);
          }
      }
      export  default  CustomersList;
      

      Dentro del constructor, estamos inicializando el objeto state. Este almacena las variables de estado de nuestro componente utilizando una matriz customers vacía. Esta matriz almacenará clientes y una nextPageURL que contendrá la URL de la siguiente página que se obtendrá del back-end de la API. También procedemos a vincular los métodos nextPage() y handleDelete() a this para que sean accesibles desde el código HTML.

      A continuación, añada el método componentDidMount() y una llamada a getCustomers() dentro de la clase CustomersList, antes de la llave de cierre.

      El método componentDidMount() es un método de ciclo de vida del componente que se llama al crear el componente y se inserta en el DOM. getCustomers() llama al objeto Customers Service para obtener la primera página de datos y el enlace de la siguiente página del backend de Django:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      componentDidMount() {
          var  self  =  this;
          customersService.getCustomers().then(function (result) {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      

      Ahora, añada el método handleDelete(), que gestiona la eliminación de un cliente, debajo de componentDidMount():

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      handleDelete(e,pk){
          var  self  =  this;
          customersService.deleteCustomer({pk :  pk}).then(()=>{
              var  newArr  =  self.state.customers.filter(function(obj) {
                  return  obj.pk  !==  pk;
              });
              self.setState({customers:  newArr})
          });
      }
      

      El método handleDelete() llama al método deleteCustomer() para eliminar un cliente utilizando su pk (clave primaria). Si la operación se realiza correctamente, la matriz customers se filtra por el cliente eliminado.

      A continuación, añada un método nextPage() para obtener los datos de la siguiente página y actualice el enlace de la página siguiente:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      nextPage(){
          var  self  =  this;
          customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      

      El método nextPage() llama a un método getCustomersByURL(), que obtiene la URL de la página siguiente del objeto de estado, this.state.nextPageURL, y actualiza la matriz customers con los datos devueltos.

      Por último, añada el método render() del componente, que produce una tabla de clientes del estado de componente:

      ~/djangoreactproject/frontend/src/CustomersList.js

      
      ...
      render() {
      
          return (
          <div  className="customers--list">
              <table  className="table">
                  <thead  key="thead">
                  <tr>
                      <th>#</th>
                      <th>First Name</th>
                      <th>Last Name</th>
                      <th>Phone</th>
                      <th>Email</th>
                      <th>Address</th>
                      <th>Description</th>
                      <th>Actions</th>
                  </tr>
                  </thead>
                  <tbody>
                      {this.state.customers.map( c  =>
                      <tr  key={c.pk}>
                          <td>{c.pk}  </td>
                          <td>{c.first_name}</td>
                          <td>{c.last_name}</td>
                          <td>{c.phone}</td>
                          <td>{c.email}</td>
                          <td>{c.address}</td>
                          <td>{c.description}</td>
                          <td>
                          <button  onClick={(e)=>  this.handleDelete(e,c.pk) }> Delete</button>
                          <a  href={"/customer/" + c.pk}> Update</a>
                          </td>
                      </tr>)}
                  </tbody>
              </table>
              <button  className="btn btn-primary"  onClick=  {  this.nextPage  }>Next</button>
          </div>
          );
      }
      

      Este es el contenido completo del archivo:

      ~/djangoreactproject/frontend/src/CustomersList.js

      import  React, { Component } from  'react';
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      
      class  CustomersList  extends  Component {
      
      constructor(props) {
          super(props);
          this.state  = {
              customers: [],
              nextPageURL:  ''
          };
          this.nextPage  =  this.nextPage.bind(this);
          this.handleDelete  =  this.handleDelete.bind(this);
      }
      
      componentDidMount() {
          var  self  =  this;
          customersService.getCustomers().then(function (result) {
              console.log(result);
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      handleDelete(e,pk){
          var  self  =  this;
          customersService.deleteCustomer({pk :  pk}).then(()=>{
              var  newArr  =  self.state.customers.filter(function(obj) {
                  return  obj.pk  !==  pk;
              });
      
              self.setState({customers:  newArr})
          });
      }
      
      nextPage(){
          var  self  =  this;
          console.log(this.state.nextPageURL);        
          customersService.getCustomersByURL(this.state.nextPageURL).then((result) => {
              self.setState({ customers:  result.data, nextPageURL:  result.nextlink})
          });
      }
      render() {
      
          return (
              <div  className="customers--list">
                  <table  className="table">
                  <thead  key="thead">
                  <tr>
                      <th>#</th>
                      <th>First Name</th>
                      <th>Last Name</th>
                      <th>Phone</th>
                      <th>Email</th>
                      <th>Address</th>
                      <th>Description</th>
                      <th>Actions</th>
                  </tr>
                  </thead>
                  <tbody>
                  {this.state.customers.map( c  =>
                      <tr  key={c.pk}>
                      <td>{c.pk}  </td>
                      <td>{c.first_name}</td>
                      <td>{c.last_name}</td>
                      <td>{c.phone}</td>
                      <td>{c.email}</td>
                      <td>{c.address}</td>
                      <td>{c.description}</td>
                      <td>
                      <button  onClick={(e)=>  this.handleDelete(e,c.pk) }> Delete</button>
                      <a  href={"/customer/" + c.pk}> Update</a>
                      </td>
                  </tr>)}
                  </tbody>
                  </table>
                  <button  className="btn btn-primary"  onClick=  {  this.nextPage  }>Next</button>
              </div>
              );
        }
      }
      export  default  CustomersList;
      

      Ahora que hemos creado el componente CustomersList para visualizar la lista de clientes, podemos añadir el componente que gestiona la creación y la actualización de clientes.

      Paso 8: incorporación del componente de React de creación y actualización de clientes

      En este paso, crearemos el componente CustomerCreateUpdate, que se encargará de crear y actualizar clientes. Lo hará al proporcionar una forma que los usuarios puedan utilizar para ingresar datos sobre un cliente nuevo o actualizar una entrada existente.

      En frontend/src, cree un archivo CustomerCreateUpdate.js:

      • nano ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      Añada el siguiente código para crear un componente de React, importando React y Component:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      import  React, { Component } from  'react';
      

      A continuación, también podemos importar e instanciar la clase CustomersService que creamos en el paso anterior, que proporciona métodos que interactúan con el backend de API REST:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      ...
      import  CustomersService  from  './CustomersService';
      
      const  customersService  =  new  CustomersService();
      

      Luego, cree un componente CustomerCreateUpdate que extienda Component para crear y actualizar clientes:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      class  CustomerCreateUpdate  extends  Component {
      
          constructor(props) {
              super(props);
          }
      
      }
      export default CustomerCreateUpdate;
      

      Dentro de la definición de clase, añada el método render() del componente, que produce un formulario HTML que toma información sobre el cliente:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      render() {
              return (
                <form onSubmit={this.handleSubmit}>
                <div className="form-group">
                  <label>
                    First Name:</label>
                    <input className="form-control" type="text" ref='firstName' />
      
                  <label>
                    Last Name:</label>
                    <input className="form-control" type="text" ref='lastName'/>
      
                  <label>
                    Phone:</label>
                    <input className="form-control" type="text" ref='phone' />
      
                  <label>
                    Email:</label>
                    <input className="form-control" type="text" ref='email' />
      
                  <label>
                    Address:</label>
                    <input className="form-control" type="text" ref='address' />
      
                  <label>
                    Description:</label>
                    <textarea className="form-control" ref='description' ></textarea>
      
      
                  <input className="btn btn-primary" type="submit" value="Submit" />
                  </div>
                </form>
              );
        }
      

      Para cada elemento de entrada del formulario, el método añade una propiedad ref para acceder al valor del elemento del formulario y establecerlo.

      A continuación, por encima del método render(), defina un método handleSubmit(event) de modo que tenga la funcionalidad correcta cuando un usuario haga clic en el botón de enviar:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleSubmit(event) {
          const { match: { params } } =  this.props;
          if(params  &&  params.pk){
              this.handleUpdate(params.pk);
          }
          else
          {
              this.handleCreate();
          }
          event.preventDefault();
      }
      
      ...
      

      El método handleSubmit(event) gestiona el envío del formulario y, dependiendo de la ruta, llama al método handleUpdate(pk) para actualizar el cliente con la pk correcta, o el método handleCreate() para crear un nuevo cliente. Procederemos a definir estos métodos en breve.

      De nuevo en el constructor del componente, vincule el método handleSubmit() recientemente añadido a this para poder acceder a él en su formulario:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      ...
      class CustomerCreateUpdate extends Component {
      
      constructor(props) {
          super(props);
          this.handleSubmit = this.handleSubmit.bind(this);
      }
      ...
      

      A continuación, defina el método handleCreate() para crear un cliente a partir de los datos del formulario. Encima del método handleSubmit(event), añada el siguiente código:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleCreate(){
          customersService.createCustomer(
              {
              "first_name":  this.refs.firstName.value,
              "last_name":  this.refs.lastName.value,
              "email":  this.refs.email.value,
              "phone":  this.refs.phone.value,
              "address":  this.refs.address.value,
              "description":  this.refs.description.value
              }).then((result)=>{
                      alert("Customer created!");
              }).catch(()=>{
                      alert('There was an error! Please re-check your form.');
              });
      }
      
      ...
      

      El método handleCreate() se utilizará para crear un cliente a partir de los datos ingresados. Llama al método CustomersService.createCustomer() correspondiente que provoca que la API real llame al backend para crear un cliente.

      A continuación, por debajo del método handleCreate(), defina el método handleUpdate(pk) para implementar actualizaciones:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      handleUpdate(pk){
      customersService.updateCustomer(
          {
          "pk":  pk,
          "first_name":  this.refs.firstName.value,
          "last_name":  this.refs.lastName.value,
          "email":  this.refs.email.value,
          "phone":  this.refs.phone.value,
          "address":  this.refs.address.value,
          "description":  this.refs.description.value
          }
          ).then((result)=>{
      
              alert("Customer updated!");
          }).catch(()=>{
              alert('There was an error! Please re-check your form.');
          });
      }
      

      El método updateCustomer() actualizará un cliente por pk, utilizando la nueva información del formulario de información de clientes. Llama al método customersService.updateCustomer().

      A continuación, añada un método componentDidMount(). Si el usuario visita una ruta customer/:pk, queremos que el formulario se complete con información relacionada con el cliente utilizando la clave primaria de la URL. Para ello, podemos añadir el método getCustomer(pk) una vez que el componente se monte en el evento de ciclo de vida de componentDidMount(). Añada el siguiente código por debajo del constructor del componente para añadir este método:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      
      ...
      componentDidMount(){
          const { match: { params } } =  this.props;
          if(params  &&  params.pk)
          {
              customersService.getCustomer(params.pk).then((c)=>{
                  this.refs.firstName.value  =  c.first_name;
                  this.refs.lastName.value  =  c.last_name;
                  this.refs.email.value  =  c.email;
                  this.refs.phone.value  =  c.phone;
                  this.refs.address.value  =  c.address;
                  this.refs.description.value  =  c.description;
              })
          }
      }
      

      Este es el contenido completo del archivo:

      ~/djangoreactproject/frontend/src/CustomerCreateUpdate.js

      import React, { Component } from 'react';
      import CustomersService from './CustomersService';
      
      const customersService = new CustomersService();
      
      class CustomerCreateUpdate extends Component {
          constructor(props) {
              super(props);
      
              this.handleSubmit = this.handleSubmit.bind(this);
            }
      
            componentDidMount(){
              const { match: { params } } = this.props;
              if(params && params.pk)
              {
                customersService.getCustomer(params.pk).then((c)=>{
                  this.refs.firstName.value = c.first_name;
                  this.refs.lastName.value = c.last_name;
                  this.refs.email.value = c.email;
                  this.refs.phone.value = c.phone;
                  this.refs.address.value = c.address;
                  this.refs.description.value = c.description;
                })
              }
            }
      
            handleCreate(){
              customersService.createCustomer(
                {
                  "first_name": this.refs.firstName.value,
                  "last_name": this.refs.lastName.value,
                  "email": this.refs.email.value,
                  "phone": this.refs.phone.value,
                  "address": this.refs.address.value,
                  "description": this.refs.description.value
              }          
              ).then((result)=>{
                alert("Customer created!");
              }).catch(()=>{
                alert('There was an error! Please re-check your form.');
              });
            }
            handleUpdate(pk){
              customersService.updateCustomer(
                {
                  "pk": pk,
                  "first_name": this.refs.firstName.value,
                  "last_name": this.refs.lastName.value,
                  "email": this.refs.email.value,
                  "phone": this.refs.phone.value,
                  "address": this.refs.address.value,
                  "description": this.refs.description.value
              }          
              ).then((result)=>{
                console.log(result);
                alert("Customer updated!");
              }).catch(()=>{
                alert('There was an error! Please re-check your form.');
              });
            }
            handleSubmit(event) {
              const { match: { params } } = this.props;
      
              if(params && params.pk){
                this.handleUpdate(params.pk);
              }
              else
              {
                this.handleCreate();
              }
      
              event.preventDefault();
            }
      
            render() {
              return (
                <form onSubmit={this.handleSubmit}>
                <div className="form-group">
                  <label>
                    First Name:</label>
                    <input className="form-control" type="text" ref='firstName' />
      
                  <label>
                    Last Name:</label>
                    <input className="form-control" type="text" ref='lastName'/>
      
                  <label>
                    Phone:</label>
                    <input className="form-control" type="text" ref='phone' />
      
                  <label>
                    Email:</label>
                    <input className="form-control" type="text" ref='email' />
      
                  <label>
                    Address:</label>
                    <input className="form-control" type="text" ref='address' />
      
                  <label>
                    Description:</label>
                    <textarea className="form-control" ref='description' ></textarea>
      
      
                  <input className="btn btn-primary" type="submit" value="Submit" />
                  </div>
                </form>
              );
            }  
      }
      
      export default CustomerCreateUpdate;
      

      Con el componente CustomerCreateUpdate creado, podemos actualizar el componente principal App para añadir enlaces a los diferentes componentes que hemos creado.

      Paso 9: actualización del componente principal App

      En esta sección, actualizaremos el componente App de nuestra aplicación para crear enlaces a los componentes que hemos creado en los pasos anteriores.

      Desde la carpeta frontend, ejecute el siguiente comando para instalar React Router, que le permite añadir enrutamiento y navegación entre varios componentes de React:

      • cd ~/djangoreactproject/frontend
      • npm install --save react-router-dom

      A continuación, abra ~/djangoreactproject/frontend/src/App.js:

      • nano ~/djangoreactproject/frontend/src/App.js

      Elimine todo lo que haya allí y añada el siguiente código para importar las clases necesarias para incorporar enrutamiento. Estas incluyen BrowserRouter, que crea un componente Router, y Route, que crea un componente de ruta:

      ~/djangoreactproject/frontend/src/App.js

      import  React, { Component } from  'react';
      import { BrowserRouter } from  'react-router-dom'
      import { Route, Link } from  'react-router-dom'
      import  CustomersList  from  './CustomersList'
      import  CustomerCreateUpdate  from  './CustomerCreateUpdate'
      import  './App.css';
      

      BrowserRoutermantiene la IU sincronizada con la URL utilizando la ](https://developer.mozilla.org/en-US/docs/Web/API/History_API)API de historial HTML5[.

      A continuación, cree un diseño base que proporcione el componente básico que envolverá el componente BrowserRouter:

      ~/djangoreactproject/frontend/src/App.js

      ...
      
      const  BaseLayout  = () => (
      <div  className="container-fluid">
          <nav  className="navbar navbar-expand-lg navbar-light bg-light">
              <a  className="navbar-brand"  href="https://www.digitalocean.com/#">Django React Demo</a>
              <button  className="navbar-toggler"  type="button"  data-toggle="collapse"  data-target="#navbarNavAltMarkup"  aria-controls="navbarNavAltMarkup"  aria-expanded="false"  aria-label="Toggle navigation">
              <span  className="navbar-toggler-icon"></span>
          </button>
          <div  className="collapse navbar-collapse"  id="navbarNavAltMarkup">
              <div  className="navbar-nav">
                  <a  className="nav-item nav-link"  href="/">CUSTOMERS</a>
                  <a  className="nav-item nav-link"  href="http://www.digitalocean.com/customer">CREATE CUSTOMER</a>
              </div>
          </div>
          </nav>
          <div  className="content">
              <Route  path="/"  exact  component={CustomersList}  />
              <Route  path="/customer/:pk"  component={CustomerCreateUpdate}  />
              <Route  path="/customer/"  exact  component={CustomerCreateUpdate}  />
          </div>
      </div>
      )
      

      Utilizamos el componente Route para definir las rutas de nuestra aplicación; el componente que el enrutador debe cargar cuando se encuentra una coincidencia. Cada ruta requiere un path que especifique la ruta que se debe seguir y un component que indique el componente que se debe cargar. La propiedad exact le indica al enrutador que siga la trayectoria exacta.

      Por último, cree el componente App, el componente root o de nivel superior de nuestra aplicación de React:

      ~/djangoreactproject/frontend/src/App.js

      ...
      
      class  App  extends  Component {
      
      render() {
          return (
          <BrowserRouter>
              <BaseLayout/>
          </BrowserRouter>
          );
      }
      }
      export  default  App;
      

      Hemos envuelto el componente BaseLayout con el componente BrowserRouter, dado que nuestra aplicación está diseñada para ejecutarse en un navegador.

      El archivo completo se ve de este modo:

      ~/djangoreactproject/frontend/src/App.js

      import React, { Component } from 'react';
      import { BrowserRouter } from 'react-router-dom'
      import { Route, Link } from 'react-router-dom'
      
      import  CustomersList from './CustomersList'
      import  CustomerCreateUpdate  from './CustomerCreateUpdate'
      import './App.css';
      
      const BaseLayout = () => (
        <div className="container-fluid">
      <nav className="navbar navbar-expand-lg navbar-light bg-light">
        <a className="navbar-brand" href="https://www.digitalocean.com/#">Django React Demo</a>
        <button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
          <span className="navbar-toggler-icon"></span>
        </button>
        <div className="collapse navbar-collapse" id="navbarNavAltMarkup">
          <div className="navbar-nav">
            <a className="nav-item nav-link" href="/">CUSTOMERS</a>
            <a className="nav-item nav-link" href="http://www.digitalocean.com/customer">CREATE CUSTOMER</a>
      
          </div>
        </div>
      </nav>  
      
          <div className="content">
            <Route path="/" exact component={CustomersList} />
            <Route path="/customer/:pk"  component={CustomerCreateUpdate} />
            <Route path="/customer/" exact component={CustomerCreateUpdate} />
      
          </div>
      
        </div>
      )
      
      class App extends Component {
        render() {
          return (
            <BrowserRouter>
              <BaseLayout/>
            </BrowserRouter>
          );
        }
      }
      
      export default App;
      

      Ahora que añadimos enrutamiento a nuestra aplicación, estamos listos para probarla. Navegue a http://localhost:3000. Debería ver la primera página de la aplicación:

      Página de inicio de la aplicación

      Con la implementación de esta aplicación, ahora, cuenta con la base para una aplicación de CRM.

      Conclusión

      En este tutorial, usted creó una aplicación de prueba utilizando Django y React. Utilizó Django REST framework para crear la API REST, Axios para consumir la API y Bootstrap 4 para dar estilo a su CSS. Puede encontrar el código fuente de este proyecto en este repositorio de GitHub.

      En la configuración de este tutorial, se utilizaron aplicaciones de front-end y back-end. Para obtener un enfoque diferente de la integración de React con Django, consulte este tutorial y este otro tutorial.

      Para obtener más información sobre la creación de aplicaciones con Django, puede seguir la serie de desarrollo de Django. También puede consultar la documentación oficial de Django.



      Source link