Servidor Express

Descripción

  • Montar un servidor web mediante paquetes propios de node

Objetivos

  • Entender la arquitectura de Express

    • Middleware

    • Uso de paquetes para extender la funcionalidad de Express

  • Creación de un servidor web que sirva:

    • Contenido estático

    • Contenido dinámico en base a templates

    • Content-type en JSON (para API posterior)

Empezamos proyecto

Documentación

express

  • Instalar express mediante uno de los comandos siguientes:

    npm install --save express@4.16.3
    npm i -S express@4.16.3
  • Creamos el fichero server.js con el siguiente código:

const express = require('express')
const app = express()
app.get('/', (req, res) => {
    res.send('Hola Mundo')
})
app.listen(3000)

Ejecución

  • Debería ejecutarse de cualquiera de las siguientes maneras:

npm start
node server.js
  • Utiliza nodemon para evitar reinicios al cambiar código

Prueba funcionamiento

  • Se echa de menos algún tipo de mensaje de arranque

  • Mira en la documentación como añadir esa opción

    • Cuando el método listen haya terminado...

Método listen

  • Permite añadir más parámetros

  • Un parámetro de tipo función (función de callback)

    • Es el único parámetro de este tipo, así los diferencia

app.listen([port[, host[, backlog]]][, callback])
app.listen(3000, () => {
  console.log('Servidor web arrancado en el puerto 3000')
})

Añadir otra ruta

  • Utiliza el plugin ExpressSnippet de Visual Code para completado

    • Comprueba su funcionamiento con el app.listen

  • Datos nueva ruta:

    • URL: /contactar

    • Muestre el mensaje Página para contactar

Código ruta contactar

app.get('/contactar', (req, res) => {
    res.send('Página para contactar')
})

Uso de JSON

  • Vamos a devolver un JSON en vez de un string:

app.get('/contactar', (req, res) => {
    res.send({
      nombre: 'pepito'
      email: 'pepito@gmail.com'
    })
})

Comprobar JSON

  • Utiliza algún plugin de formateo de JSON dentro del navegador

    • El JSON puede ser más complejo que el anterior, por ej:

      https://api.arasaac.org/api/pictograms/es/search/casa
  • Comprueba como cambia el content type

    • Utiliza las herramientas de desarrollo

    • El cambio lo hace directamente Express

  • Los datos en JSON podrían estar:

    • En una variable

    • Incluso en otro fichero

    • Recibirse vía API

    • ....

const contacto = require('./contacto.json')
app.get('/contactar', (req, res) => {
  res.send(contacto)
})

Middleware

  • Son funciones que:

    • Se registran por express mediante app.use y se ejecutan en orden

    • Tienen acceso al objeto de solicitud (req), al objeto de respuesta (res) y a la siguiente función de middleware (next)

app.use((req, res, next)=>{
  //operaciones del middleware
  next() //para ir al siguiente middleware o a la ruta
  // también podríamos hacer un send() y cortar
  // la cola de middlewares, por ej en un control de permisos
})

Ejemplo de middleware

  • Podemos crear un middleware que guarde traza de las fechas de accesos

  • Para ver las propiedades que nos hacen falta podemos usar la API de Express

  • Es importante el orden

    • Entre los middlewares

    • Antes que las peticiones get, post....

var app = express()

app.use(function (req, res, next) {
  var now = new Date().toString()
  console.log(`Time: ${now} ${req.method} ${req.url}`)
  next()
})

Logs a fichero

  • Completa el middleware anterior para que guarde los cambios en el fichero server.log

  • Piensa donde se debe poner el next() y si debes utilizar un método síncrono o asíncrono

Solución logs a fichero

app.use((req, res, next) => {
  var now = new Date().toString()
  var log = `${now}: ${req.method} ${req.url}`
  console.log(log)
  fs.appendFile('server.log', `${log}\n`, (err) => {
    if (err) console.log(`No se ha podido usar el fichero de log:  ${err}`)
  })
  next()
})

Más sobre logs

  • Podemos querer utilizar distintos transports o medios para logs

    • Ficheros

    • Consola

  • Distintos niveles (debug, err, warning...)

  • Distintos formatos de visualización (colores, negrita...)

  • Con posibilidad de ejecución de queries

  • ....

Uso de contenido estático

  • Crea un fichero .html en la carpeta public

    • Ayúdate de emmet: ! + tab

  • Express ya tiene un middleware integrado para contenido estático

    • No deja de ser una función como las vistas anteriormente

    • No necesitamos importarla mediante un require

    • Una vez importado, hace un send() si existe el fichero, si no, un next()

Configuración express para contenido estático

const staticRoute = path.join(__dirname, 'public')
app.use(express.static(staticRoute))
  • __dirname es la raíz del proyecto

  • path.join para que sea multiplataforma

  • Podríamos configurar un directorio virtual (static para public)

const staticRoute = path.join(__dirname, 'public')
app.use('/static', express.static(staticRoute))

Template engine

  • Mostrar un index.html está bien pero puede que necesitemos:

    • Inyectar valores en el html

    • "includes" para footer, header... (patrón diseño DRY)

  • Tenemos que elegir un motor de plantillas

Configuración de hbs

  • Instalación

    npm i -S hbs
  • Configuración

    // const hbs = require('hbs')
    app.set('view engine', 'hbs'); // clave valor
    • No hacemos un require porque no usamos ninguna función

    • express lo llama internamente

  • Indicamos a nodemon los tipos de ficheros a monitorizar (por defecto solo js):

"scripts": {
  "start": "nodemon server.js -e js,hbs"
},

Uso de hbs

  • Definimos una carpeta views donde irán las templates

  • Fichero views/contactar.hbs:

  ...
  <body>
    <h1>{{pageTitle}}</h1>
    <p>Aquí iría el formulario de contacto</p>

    <footer>
      <p>Copyright {{currentYear}}</p>
    </footer>
  </body>
  ....
  • Ejecutamos el método res.render() en vez de res.send()

    • Admite un objeto como segundo parámetro para pasar variables

app.get('/contactar', (req, res) => {
  res.render('contactar.hbs', {
    pageTitle: 'Contactar',
    currentYear: new Date().getFullYear()
  })
})

Partials mediante handlebars

  • Registramos el directorio donde se van a guardar:

const hbs = require('hbs')
hbs.registerPartials(path.join(__dirname, 'views', 'partials'))
app.set('view engine', 'hbs') // clave valor
  • Creamos fichero views/partials/footer.hbs:

  <footer>
    <p>Copyright {{getCurrentYear}}</p>
  </footer>
  • Lo incluimos dentro de nuestro fichero views/contactar.hbs:

  ...
  <body>
    <h1>{{pageTitle}}</h1>
    <p>Aquí iría el formulario de contacto</p>
    {{> footer}}
    <!-- con la linea anterior
       tendríamos el footer mediante partials -->

  </body>
  ....

Ejercicio templates

  • Añade una página de inicio además de contactar

  • Ambas deben cargar su correspondiente template que además cargará un partial para el header y otro para el footer

Uso de helpers

  • Se registran funciones que devuelven un código dinámico

  • Se pueden inyectar en cualquier template o partial.

hbs.registerHelper('getCurrentYear', () => new Date().getFullYear())
// con paso de parámetro:
hbs.registerHelper('toUpperCase', text => text.toUpperCase())
  • Podríamos eliminar el paso de currentYear a las vistas y utilizar el helper:

<footer>
  <p>Copyright {{getCurrentYear}}</p>
  <p>{{toUpperCase "Licencia MIT"}}</p>
 {{>header}}
</footer>

¿Continuamos?

Last updated