EXPRESS.ROUTER()

&

COMMONJS

EXPRESS.ROUTER()

Use the express.Router class to create modular, mountable route handlers. A Router instance is a complete middleware and routing system; for this reason, it is often referred to as a “mini-app”.

Repaso Live Coding

SIN routers

 

var express = require('express')
var app = express()

var users = [];

app.get('/', function (req, res) {
  res.json(users);
})

app.get('/:id', function (req, res) {
    const id = req.params.id;
    const user = users.find(u => u.id == id);
    return res.json(user)
})

app.listen(5000)

server.js

const express = require('express');
const app = express();

app.use(express.json());


const usersRouter = require('./api/users');
app.use('/api/users', usersRouter);

app.listen(5000);

server.js

ROUTER

var router = require('express').Router()

var users = [];

router.get('/', function (req, res) {
  res.json(users);
})

router.get('/:id', function (req, res) {
    const id = +req.params.id;
    const user = users.find(u => u.id === id);
    return res.send(user)
})

module.exports = router

/api/users/index.js

Ahora nuestro server.js o index.js

const express = require('express');

const app = express();

// Resources routers 
const usersRouter = require('./api/users');

// Applying middlewares and routes
app.use(express.json());
app.use('/api/users', usersRouter);

app.listen(5000);

¿Qué estamos ganando?

  • Código mucho más fácil de mantener.
  • Un fichero de comportamientos por cada recurso y no todo en un index.js o server.js
  • Poder dividir el trabajo en equipo.
  • Poder más tarde testear cada router por separado.
  • Un largo etcétera de beneficios creanme.
var axios = require('axios');
var cheerio = require('cheerio');

...

module.exports = {
    getHTML : useAxiosToExtractDOM,
    extractInfo : parseDOMToDataUsingCheerio
}

function useAxiosToExtractDOM(url) { }
function parseDOMToDataUsingCheerio(html) { }

utils/extractor.js

var app = require('express')(); 
var extractor = require('./utils/extractor');

app.get('/extractInfo', (req, res) => {
      const url = req.queryParams.url;      
      extractor.getHTML(url)
        .then(extractor.extracInfo)
        .then( data => res.json(data))
}

server.js

var axios = require('axios');
var cheerio = require('cheerio');

...

module.exports = {
    getHTML : useAxiosToExtractDOM,
    extractInfo : parseDOMToDataUsingCheerio
}

router + module.exports

const router = require('express').Router();

router.get('/', ( req, res) => {
    // Code here
});
router.get('/:id', ( req, res) => {
    // Code here
});
router.post('/', ( req, res) => {
    // Code here
});

module.exports = router;

api/users/index.js

Creamos el router en un fichero a parte y lo exportamos

router + module.exports

const express = require('express');
const app = express();

// Resources routers 
const usersRouter = require('./api/users');

app.use('/api/users', usersRouter);

server.js

const router = require('express').Router();

router.get('/', ( req, res) => {
    // Code here
});
router.get('/:id', ( req, res) => {
    // Code here
});
router.post('/', ( req, res) => {
    // Code here
});

module.exports = router;

api/users/index.js

Ponemos esto en práctica

Ejercicio en directo

Refactorizamos el ejercicio de ayer de la todo list

Refactorizamos el ejercicio de ayer de la api de twitter

¡ More cleaning code ! 

Y si ...

const router = require('express').Router();
const magic = require('./whereTheMagicHappens');

router.get('/', magic.doWhatever);
router.get('/:id', magic.doWhatever);
router.post('/', magic.doWhatever);

module.exports = router;

/api/users/index.js

Y si ...

Y si ...

index.js 10 líneas

api/users/index.js 8 líneas

api/users/users.controller.js 60 líneas

Ponemos esto en práctica

Live coding

¡A trabajar otra vez!

Primera API con 2 recursos

Queremos desarrollar una api restful con node para un proyecto copia de twitter. Aunque de momento, será solo una primera versión con usuarios y tweets como si fuese un blog personal.

USUARIOS

username* : string<uniq>

name? : string

email* : string

tweetsIDs : string[]

 

TWEETS

id* : string<uniq>

text : string

owner : string<ID>

createdAt : timestamp

Esto sería un id de los tweets que este usuario había creado

"2 tablas"

Primera API

  • Deberán estar implementadas las funcionalidades de:
    • Crear un nuevo usuario
    • Borrar un usuario 
    • Editar el email de un usuario o el nombre (PATCH)
    • Subir un tweet nuevo por parte de un usuario
    • Ir a buscar un tweet en concreto por su id
    • Borrar un tweet por su id
    • *Opcional: Obtener todos los tweets ordenados tanto asc como desc por la fecha de subida
    • *Opcional: Guardar los tweets y los usuarios en un fichero db.json y actualizarlo con cada cambio.
    • OBLIGATORIO: Usar router para cada recurso

OJO ! CONTROLA QUE LA API NO SE PUEDA QUEDAR PILLADA EN NINGÚN MOMENTO, controla campos vacíos, request inválidas, etc etc y devuelve el error en la respuesta y el código correcto para dicho caso

PERSISTENCIA DE NUESTROS DATOS

Esta vez, no en ficheros, sino en una base de datos

SQL

NO-SQL

GRAFOS 

¿ WHY MONGO?

Base de datos basada en documentos

Nos permite guardar la información como si de un objeto se tratase.

Los documentos del mismo tipo no están obligados a tener unos campos si o si e incluso estos podrán variar

EJEMPLO

DESCARGAMOS E INSTALAMOS

MONGODB

Si todo ha ido bien ...

Ejecutamos en una terminal 

mongod o la ruta completa al .exe

y nos dirá que está listo y escuchando en el puerto 27017

Si queremos acceder a la consola

Para acceder a la consola de mongo una vez tengamos el mongod corriendo debemos de ejecutar el comando/ejecutable mongo

¡ AL LÍO !

Alternativas a la consola

Conectando mongo con nodejs

Librerías ODM

Hay módulos que nos permiten interacturar con los Documentos y collecciones de nuestra base de datos como si de objetos y arrays normales se tratasen :O 
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/todolist');

var TODOschema = mongoose.Schema({
    text: String,
    id : String,
    createdAt : Number,
    isCompleted : Boolean
});

var TODO = mongoose.model('todo', TODOschema);

TODO.find({}, (err, res) => {
    console.log(res);
})
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/todolist');

var TODOschema = mongoose.Schema({
    text: String,
    id : String,
    createdAt : Number,
    isCompleted : Boolean
});

var TODO = mongoose.model('todo', TODOschema);

TODO.find({}, (err, res) => {
    console.log(res);
})
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/todolist');

var TODOschema = mongoose.Schema({
    text: String,
    id : String,
    createdAt : Number,
    isCompleted : Boolean
});

var TODO = mongoose.model('todo', TODOschema);

TODO.find({}, (err, res) => {
    console.log(res);
})
var TODO = mongoose.model('todo', TODOschema);

TODO.find({})

TODO.find({createdAt : 1521713274870})

TODO.find({createdAt : { $gt : 1521713274870} })

TODO.find({ otros : { $in : [ {hola : "mundo" } ]} })

// Proyecciones
TODO.find({createdAt : 1521713274870}, "text createdAt" )
Person.
  find({
    occupation: /host/,
    'name.last': 'Ghost',
    age: { $gt: 17, $lt: 66 },
    likes: { $in: ['vaporizing', 'talking'] }
  })
    .limit(10)
    .sort({ occupation: -1 })
    .select({ name: 1, occupation: 1 })
    .exec(callback);

// Using query builder
Person.
  find({ occupation: /host/ })
    .where('name.last').equals('Ghost')
    .where('age').gt(17).lt(66)
    .where('likes').in(['vaporizing', 'talking'])
    .limit(10)
    .sort('-occupation')
    .select('name occupation')
    .exec(callback);

var TODO = mongoose.model('todo', TODOschema);

var nuevo = new TODO({
    text : 'Hello',
    id : "asd",
    createdAt : 13131312313,
    isCompleted : false
})
nuevo.save();

Insertando documentos

TODO.create({ text: 'hola' }, function (err, small) {
  if (err) return res.status(500).json(err);
  // saved!
});
TODO.create({ text: 'hola' }).then(doc) {
  // confirmed
});

Insertando documentos

TODO.create({ text: 'hola' }).then(doc) {
  // confirmed
});

var TODO = mongoose.model('todo', TODOschema);

TODO.findOne ( { "text" : "hola" } , (err , doc) => {
    
    doc.text = 'Nuevo texto';

    doc.save();

}

Actualizando documentos


var TODO = mongoose.model('todo', TODOschema);

TODO.findOne ( { "text" : "hola" })
    .update( { "text" : "adios" } ) 
    .exec()

var TODO = mongoose.model('todo', TODOschema);

TODO.findOne ( { "text" : "hola" } , (err , doc) => {
    
    doc.remove();

}

Borrando documentos


var TODO = mongoose.model('todo', TODOschema);

TODO.findOne( { "text" : "hola" })
  	.remove()

Borrando documentos

Sigamos

Vamos a cobrar más

Vamos a cobrar más

const TODOModel = require('./todos.model');


function getAllTODOs(req, res) {
    TODOModel.find()
        .then(response => {
            console.log(response);
            res.json(response);
        })

}

Fichero : /api/todos/todos.controller.js

Vamos a cobrar más

const TODOModel = require('./todos.model');


function getTODOById(req, res) {
    TODOModel.findOne({ id : req.params.id})
        .then(response => {
            res.json(response);
        })
}

Fichero : /api/todos/todos.controller.js

Vamos a cobrar más

const TODOModel = require('./todos.model');

function getTODOById(req, res) {
    TODOModel.findById(req.params.id)
        .then(response => {
            res.json(response);
        })
        .catch(err => {
            res.json(err);
        })
}

Fichero : /api/todos/todos.controller.js

SI USAMOS LOS _ID de mongo quedaría así

RESUMEN

async function resumenDeTodo( req, res ){

 // Buscar por el ID
 const encontrado = await model.findById(req.params.id)

 // Insertar nuevos
 const insertado = await model.create(req.body)
 
 // Buscar por algún campo
 const encontrados = await model.find({ name : req.params.name})
 
 // Borrar por el ID
 const borradoPorId = await model.findByIdAndRemove(req.params.id)
 
 // Encontrar y editar por el ID
 const borradoPorNombre = await model.findByIdAndUpdate(req.params.id , req.body)

}

También son promesas

Vamos a cobrar más

A currar

Guarda tus datos del proyecto de todolist en mongodb y usa mongoose para conectar tu api con la base de datos. NO HAGAS VALIDACIONES 

¡UN PASO MÁS ALLÁ !

MONGOOSE TE PERMITE PONER VALIDACIONES

const mongoose = require('mongoose');

var TODOschema = mongoose.Schema({
    text: String,
    id: String,
    createdAt: Number,
    isCompleted: Boolean
});

var TODO = mongoose.model('todo', TODOschema);

module.exports = TODO;
var breakfastSchema = new Schema({
      eggs: {
        type: Number,
        min: [6, 'Too few eggs'],
        max: 12
      },
      bacon: {
        type: Number,
        required: [true, 'Why no bacon?']
      },
      drink: {
        type: String,
        enum: ['Coffee', 'Tea'],
        required: function() {
          return this.bacon > 3;
        }
      }
    });

¿Cómo uso esto?

var Breakfast = db.model('Breakfast', breakfastSchema);

var badBreakfast = new Breakfast({
  eggs: 2,
  bacon: 1,
  drink: 'Milk'
});

var error = badBreakfast.validateSync();

console.log(error.errors); 
  // { 
  //   eggs : 'Too few eggs',
  //   drink : '`Milk` is not a valid enum value for path `drink`.'
  // }

Otra ejemplo más

 var userSchema = new Schema({
  phone: {
    type: String,
    validate: {
      validator: function(v) {
        return /\d{3}-\d{3}-\d{4}/.test(v);
      },
      message: '{VALUE} is not a valid phone number!'
    },
    required: [true, 'User phone number required']
  }
});

MIDDLEWARES

MIDDLEWARES 

EVERYWHERE 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

MIDDLEWARES 

app.use( middleware )


app.use( (req, res, next) => {
    console.log(`Hemos recibido una solicitud a ${req.url}`);
    next();
})

Las funciones de middleware son funciones que tienen acceso al objeto de solicitud (req), al objeto de respuesta (res) y a la siguiente función de middleware en el ciclo de solicitud/respuestas de la aplicación. La siguiente función de middleware se denota normalmente con una variable denominada next.

IMPORTA EL ORDEN

app.use(express.json());
app.use( (req, res, next) => {
    console.log(`Yo me ejecuto primero`);
    req.invalidUser = true;
    next();
})
app.use( (req, res, next) => {
    console.log(`Y luego yo`);
    if (req.invalidUser) return res.sendStatus(401)
    else    next()
})

app.use('/api/users', usersRouter);

IMPORTA EL ORDEN

app.use(express.json());

app.use('/api/users', usersRouter);

app.use( (req, res, next) => {
    console.log(`Yo me ejecuto primero`);
    req.invalidUser = true;
    next();
})
app.use( (req, res, next) => {
    console.log(`Y luego yo`);
    if (req.invalidUser) return res.status(401).send('Invalid Request')
    else    next()
})

ERROR HAMIGOH

ALGUNOS MIDDLEWARES

const express = require('express');
const morgan = require('morgan');
const app = express();

app.use(morgan('combined'));

app.listen(5000);

ALGUNOS MIDDLEWARES

// Use the session middleware
app.use(session({ 
            secret: 'keyboard cat', 
            cookie: { maxAge: 60000 }, 
            resave: true, 
            saveUninitialized: true}))
 
// Access the session as req.session
app.get('/', function(req, res, next) {
  if (req.session.views) {
    req.session.views++;
    res.setHeader('Content-Type', 'text/html');
    res.send(`<p>views: ${req.session.views}</p>
            <p>expires in: ${req.session.cookie.maxAge / 1000}s</p>`);
  } else {
    req.session.views = 1;
    res.end('welcome to the session demo. refresh!');
  }
})

ALGUNOS MIDDLEWARES

errorhandler + otros loggers

const app = require('express')();
const errorhandler = require('errorhandler')
const notifier = require('node-notifier');

app.use(errorhandler( { log : errorNotification } ));

app.listen(5000);

function errorNotification (err, str, req) {
  var title = 'Error in ' + req.method + ' ' + req.url

  notifier.notify({
    title: title,
    message: str
  })
}

errorhandler + otros loggers

const app = require('express')();
const errorhandler = require('errorhandler')
const notifier = require('node-notifier');

if (process.env.NODE_ENV === 'development'){
    app.use(errorhandler( { log : errorNotification } ));
}

app.listen(5000);

function errorNotification (err, str, req) {
  var title = 'Error in ' + req.method + ' ' + req.url

  notifier.notify({
    title: title,
    message: str
  })
}

ALGUNOS MIDDLEWARES

ALGUNOS MIDDLEWARES

var compression = require('compression')
var express = require('express')

var app = express()

// compress all responses
app.use(compression())

// add all routes

ALGUNOS MIDDLEWARES

var cors = require('cors');
var app = require('express')();

app.use(cors());
{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}
var app = require('express')();
var cors = require('cors')

var corsOptions = {
  origin: ['http://example.com' , 'http://example2.com' ]
}

app.use(cors(corsOptions));

app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

AWESOME !!!

SPEGC Agosto 2022 Full Stack. ExpressJS Router & MongoDB Día 3

By Yunior González Santana

SPEGC Agosto 2022 Full Stack. ExpressJS Router & MongoDB Día 3

Nuestra API empieza a coger color ! API ordenada con RouterJS y Model conectado con MongoDB

  • 200