Node.js: Modularizando

Nuestro index.js actualmente luce así:

index.js

import express from 'express' import bodyParser from 'body-parser' import mongoose from 'mongoose' //Mongoose mongoose.connect('mongodb://localhost/news') // Mongoose models and schemas const postSchema = new mongoose.Schema({ title: String, content: String, upvotes: { type: Number, default: 0 }, createdAt: { type: Date, default: Date.now } }) const Post = mongoose.model('Post', postSchema) // Express configuration const app = express() app.use(bodyParser.json()) // Express routes app.get('/noticias', (req, res, next) => { Post.find() .then(noticias => res.json(noticias)) .catch(next) }) app.post('/noticias', (req, res, next) => { const noticia = new Post(req.body) noticia.save() .then(noticia => res.sendStatus(200)) .catch(next) }) // Express startup const port = 3001 app.listen(port, () => { console.log(`Server running on port ${port}`) })

Como podemos notar en el mismo archivo se mezclan responsabilidades muy distintas. Si bien de momento index.js es bastante simple es de esperarse que, conforme agreguemos las funcionalidades faltantes, el mismo crezca exponencialmente.

Debemos encontrar alguna de dividir el mismo en unidades cohesivas.

Modulos (ES6)

Antes que la especificación de javascript que hoy conocemos como ES6 (o ECMAScript 2015) no existía en js ninguna forma oficial de modularizar el código. Muchas librerías proveían implementaciones parciales (incompatibles entre si) que se volvieron de a poco estandares de-facto (CommonJS, RequireJS, AMD). ES6 tomó herramientas de dichos mecanismos (principalmente de CommmonJS) y las formalizó en la especificación del lenguaje.

Por qué modulos?

Tradicionalmente las aplicaciones javascript dividían su código entre distintos archivos los cuales eran concatenados al buildear la aplicación para producir un único archivo fuente. Esta práctica, si bien solucionaba el problema inmediato, era propensa a varios problemas:

  • Era muy fácil generar colisiones de nombres entre archivos (por ejemplo: una misma variable global declarada dos veces con fines distintos).
  • Conforme a la complejidad del sistema crecía multiples referencias al mismo archivo podían resultar en el mismo código siendo ejecutado varias veces (y en un estado duplicado).

Como solución a dichos problemas los sistemas de modulos introducen al modulo como una unidad aislada:

  • Cada modulo corre posee entonces su propio scope lo que previene las colisiones de nombre.
  • Cada modulo es efectivamente un singleton que puede ser importado varias veces en distintos momentos.

Refactorizando nuestra aplicación

Comenzaremos por definir una estructura de directorios donde colocaremos nuestros archivos.

  • src/backend - aquí colocaremos todo el código perteneciente al backend (nodejs + express) de nuestra app. Moveremos nuestro archivo index.js a esta localización.
  • src/backend/models - aquí crearemos un archivo js por modelo que declaremos en mongoose.
  • src/backend/routes - crearemos un modulo que contendrá las distintas rutas de nuestra aplicación.

Primero modificaremos el archivo package.json para referenciar a la nueva ubicación de nuestro index.js

package.json

... "scripts": { "start": "babel-node -- src/backend/index.js" }, ...

Luego crearemos el archivo Post.js en src/backend/models y colocaremos en él la definición del schema.

src/backend/models/Post.js

import mongoose from 'mongoose' const postSchema = new mongoose.Schema({ title: String, content: String, upvotes: { type: Number, default: 0 }, createdAt: { type: Date, default: Date.now } }) const Post = mongoose.model('Post', postSchema) export default Post

Es importante notar lo siguiente:

  • Necesitamos importar aquí al modulo mongoose ya que haremos uso de él inmediatamente después.
  • Exportaremos el objeto Post de forma que pueda ser referenciado por otros modulos.

A continuación moveremos la configuración de nuestras rutas a un nuevo archivo llamado routes.js. Ubicaremos el mismo en src/backend/routes.

src/backend/routes/routes.js

import express from 'express' import Post from '../models/Post.js' let router = express.Router() // Express routes router.get('/noticias', (req, res, next) => { Post.find() .then(noticias => res.json(noticias)) .catch(next) }) router.post('/noticias', (req, res, next) => { const noticia = new Post(req.body) noticia.save() .then(noticia => res.sendStatus(200)) .catch(next) }) export default router

En este caso en lugar de configurar nuestro objeto app directamente procederemos a configurar, en cambio, un objeto router el cuál exportaremos para usar mas tarde.

Es util notar que debido a que nuestras rutas hacen uso del objeto Post deberemos importar el modulo Post.js que habíamos creado antes.

Para finalizar, refactorizaremos nuestro index.js para importar los modulos recién creados.

src/backend/index.js

import express from 'express' import bodyParser from 'body-parser' // Connects to mongoose import mongoose from 'mongoose' mongoose.connect('mongodb://localhost/news') // Express configuration const app = express() app.use(bodyParser.json()) import routes from './routes/routes.js' app.use(routes) // Express startup const port = 3001 app.listen(port, () => console.log(`Server running on port ${port}`))

import

TODO, hablar del algoritmo de resolucion de modulos

Luego es simplemente cuestión de reiniciar node (npm start) y nuestro proyecto debería funcionar exactamente igual que antes. Pero con código mucho mas limpio!

GitHub

Como referencia dejamos el estado del proyecto en GitHub

results matching ""

    No results matching ""