Refactorizando una aplicación Express

Archivada en Desarrollo

Refactorizando una aplicación Express

Ya sabemos que Express.js tiene una CLI que genera un proyecto, pero ahora vamos a convertir, en la medida de mis limitadas capacidades, en la sintaxis de ECMAScript 2016. Veamos si el resultado es exitoso.

Instalación de la CLI de Express

Primero, vamos a instalar el generador, instalando express como paquete global.

yarn global add express

Con esto, estamos en posibilidad de usar la CLI. Estas son las opciones de generación.

$ express -h                                                                                                                           

  Usage: express [options] [dir]

  Options:

    -h, --help           output usage information
        --version        output the version number
    -e, --ejs            add ejs engine support
        --pug            add pug engine support
        --hbs            add handlebars engine support
    -H, --hogan          add hogan.js engine support
    -v, --view <engine>  add view <engine> support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
    -c, --css <engine>   add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
        --git            add .gitignore
    -f, --force          force on non-empty directory

Generación del proyecto

Bien, vamos usar como motor de plantillas Handlebars, con la opción --view hbs, el preprocesador de estilos será Sass, --css saas y queremos que genere un archivo .gitignore. Así que esta es la línea que usaremos,

$ express --view hbs --css sass --git nspaces 

Y este es el resultado:

   create : nspaces
   create : nspaces/package.json
   create : nspaces/app.js
   create : nspaces/.gitignore
   create : nspaces/public
   create : nspaces/routes
   create : nspaces/routes/index.js
   create : nspaces/routes/users.js
   create : nspaces/views
   create : nspaces/views/index.hbs
   create : nspaces/views/layout.hbs
   create : nspaces/views/error.hbs
   create : nspaces/bin
   create : nspaces/bin/www
   create : nspaces/public/javascripts
   create : nspaces/public/images
   create : nspaces/public/stylesheets
   create : nspaces/public/stylesheets/style.sass

   install dependencies:
     $ cd nspaces && npm install

   run the app:
     $ DEBUG=nspaces:* npm start

Como podemos ver, creo cuatro directorios:

  • bin — que contiene solo un archivo, llamado www y es el que ejecuta el proyecto.
  • public — contiene los archivos de plantillas, los scripts del front-end y los estilos.
  • routes — Contiene dos archivos, uno llamado índex.js y otro llamado users.js.
  • views — Contiene plantillas de Handlebars, la base, una para errores y el índice.

Hay también un archivo app.js, uno .gitignore y un package.json.

Archivo .gitignore

Ignora los archivos comunes del desarrollo con Node.js. No hay ninguna sorpresa, así que lo dejamos como esta.

Archivo packages.json

Este es el contenido:

{
  "name": "nspaces",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.17.1",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.3",
    "express": "~4.15.2",
    "hbs": "~4.0.1",
    "morgan": "~1.8.1",
    "node-sass-middleware": "0.9.8",
    "serve-favicon": "~2.4.2"
  }
}

La principal modificación que vamos a hacer es que vamos a agregar las dependencias de Babel y modificaremos el script de arranque.

yarn add --dev babel-cli babel-preset-es2015

Esto agrega una nueva sección a nuestro archivo, que junto con la configuración de Babel, se ve así:

  "devDependencies": {
    "babel-cli": "^6.24.1",
    "babel-preset-es2015": "^6.24.1"
  },
  "babel": {
    "presets": ["es2015"]
  }

Vamos a cambiar el comando del script start, de node a babel-node y estamos listos para instalar el resto de las dependencias y probar si funciona Babel tal como está el proyecto actualmente.

$ yarn start
yarn start v0.24.5
$ babel-node ./bin/www
GET / 200 186.270 ms - 204

No hay nada que indique que el servidor ya arrancó o el puerto en el que está funcionando, pero si visitamos la página http://localhost:3000 podemos ver el resultado esperado. Ya podemos empezar a refactorizar.

Refactorizabdo app.js

Empezaremos con este archivo, cambiando las líneas como esta

var express = require('express');

a esto

import express from 'express'

Nuestros import se ven así:

import express from 'express'
import path from 'path'
import favicon from 'serve-favicon'
import logger from 'morgan'
import cookieParser from 'cookie-parser'
import bodyParser from 'body-parser'
import sassMiddleware from 'node-sass-middleware'

y no hemos roto nada. Pero ahora siguen las rutas, donde se importan archivos locales, veamos si podemos usar la misma estructura.

Los cambiamos por esto:

import index from './routes/index'
import users from './routes/users'

y todo sigue funcionando.

La línea

var app = express();

cambia a

let app = express()

La configuración del motor de plantillas no requiere nada (solo quité el punto y coma) y se ve igual

// configuracion del motor de plantillas
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'hbs')

La configuración del favicon, la bitácora, el analizador de contenido bodyParser y de cookies, cookieParser tampoco cambia.

Cambiando a SCSS

Aquí damos un pequeño brinco, porque pasamos de Sass a SCSS. El cambio es solo cuestión de gustos. La plantilla cambia a esto:

body {
  padding: 50px;
  font: 14px "Noto Sans", Helvetica, Arial, sans-serif;
}

a {
  color: indianred;
}

Dado el cambio en el procesador, de regreso en app.js hacemos los ajustes necesarios.

app.use(sassMiddleware({
  src: path.join(__dirname, 'public'),
  dest: path.join(__dirname, 'public'),
  indentedSyntax: false, // true = .sass and false = .scss
  sourceMap: true
}));

Las rutas no cambian…

app.use('/', index)
app.use('/users', users)

Ni el generador de errores 404…

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found')
  err.status = 404
  next(err)
})

El siguiente manejado de errores, tampoco lo tocamos…

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message
  res.locals.error = req.app.get('env') === 'development' ? err : {}

  // render the error page
  res.status(err.status || 500)
  res.render('error')
})

Por último, exportamos la app. Sin cambios.

module.exports = app

Refactorizando bin, primera parte

No me atrevo con este archivo, pero haremos el primer intento.

Primero cambiamos la línea que importa app, para dejarla así:

import app from '../app'    // var app = require('../app');

La siguiente línea es esta:

var debug = require('debug')('nspaces:server');

Y la cambiaré en dos líneas, creo…

import Debug from 'debug'
let debug = Debug('nspaces:server')

Ufff, que suerte. Funcionó.

Hay una función llamada normalizarPuerto, que al cambiarla a let debe colocarse antes de su primer uso, quedando así:

/**
 * Normalizamos un puerto a número, cadena o false
 */
let normalizarPuerto = (val) => {
  let port = parseInt(val, 10)
  if (isNaN(port)) return val
  if (port >= 0) return port
  return false
}

/**
 * El puerto lo obtenemos del entorno...
 */
let port = normalizarPuerto(process.env.PORT || '3000')
app.set('port', port)

Y lo mismo va a pasar con la función siHayError

/**
 * Event listener for HTTP server "error" event.
 */
let siHayError = (error) => {
  if (error.syscall !== 'listen') throw error
  let bind = typeof port == 'string' ? `Pipe ${port}` : `Puerto ${port}`
  // manejamos los errores con mensajes amigables
  switch (error.code) {
    case 'EACCES':
      console.error(`${bind} requiere de mayores privilefios`)
      process.exit(1)
      break
    case 'EADDRINUSE':
      console.error(`El ${bind} ya está en uso`)
      process.exit(1)
      break;
    default:
      throw error
  }
}

… y con alEscuchar

/**
 * alEscuchar es un evento de "escucha" del servidor HTTP.
 */
let alEscuchar = () => {
  let direccion = server.address()
  let bind = typeof direccion === 'string' ? `el pipe ${direccion}` : `el puerto ${direccion.puerto}`
  debug(`Escuchando en ${bind}`)
}

Al final el archivo se ve de esta manera:

/**
 * Module dependencies.
 */
import app from '../app'
import http from 'http'
import Debug from 'debug'

let debug = Debug('nspaces:server')

/**
 * Normalizamos un puerto a número, cadena o false
 */
let normalizarPuerto = (val) => {
  let puerto = parseInt(val, 10)
  if (isNaN(puerto)) return val
  if (puerto >= 0) return puerto
  return false
}

/**
 * Event listener for HTTP server "error" event.
 */
let siHayError = (error) => {
  if (error.syscall !== 'listen') throw error
  let bind = typeof port == 'string' ? `Pipe ${port}` : `Puerto ${port}`
  // manejamos los errores con mensajes amigables
  switch (error.code) {
    case 'EACCES':
      console.error(`${bind} requiere de mayores privilefios`)
      process.exit(1)
      break
    case 'EADDRINUSE':
      console.error(`El ${bind} ya está en uso`)
      process.exit(1)
      break;
    default:
      throw error
  }
}

/**
 * alEscuchar es un evento de "escucha" del servidor HTTP.
 */
let alEscuchar = () => {
  let direccion = server.address()
  let bind = typeof direccion === 'string' ? `el pipe ${direccion}` : `el puerto ${direccion.port}`
  debug(`Escuchando en ${bind}`)
}

/**
 * El puerto lo obtenemos del entorno...
 */
const puerto = normalizarPuerto(process.env.PORT || '3000')
app.set('port', puerto)

/**
 * Creamos el servidor HTTP...
 */
const server = http.createServer(app);

/**
 * Escuchamos el puerto determinado y todas las interfaces de red...
 */
server.listen(puerto)
server.on('error', siHayError)
server.on('listening', alEscuchar)

Refactorizando las rutas

Estos archivos son pequeños, así que es fácil refactorizarlos.

Este el archivo routes/index.js refactorizado:

import express from 'express'
let _rutaIndex = express.Router()

/* GET de la portada */
_rutaIndex.get('/', (req, res, next) => res.render('index', { title: 'nSpaces' }))

module.exports = _rutaIndex

Y este es el archivo routes/users.js

import express from 'express'
let _rutaUsers = express.Router()

/* De usuarios, creo. */
_rutaUsers.get('/', (req, res, next) => res.send(`respondiendo con un recurso`))

module.exports = _rutaUsers

Refactorizando las plantillas

Realmente la única que requiere cambios es index.hbs y solo implica cambiar el Welcome to a Bienvenido a

<h1>{{title}}</h1>
<p>Bienvenido a {{title}}</p>

Liberando la versión refactorizada

Como ya está listo el proyecto con la sintaxis de ES6, vamos a liberar una versión para que podamos usarla en futuros desarrollos.

Esta es la página del proyecto nSpaces en GitHub versión v0.1.0 firmada con GnuPG para mayor seguridad.

Javier Sanchez Toledano

Soy programador en Django+Python y WordPress. Auditor líder certificado en la norma ISO 9001:2008. Fotógrafo aficionado.
Redes Sociales:

Tlaxcala, México

Comentarios