EXPRESS.ROUTER()

&

COMMONJS

Vamos a cobrar más

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

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

¡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;

Pues podemos meterle validaciones ! 

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?

BreakfastModel.create(req.body)
	.then( creado => res.send(creado))
	.catch(error => {
  	// Diferentes posibles errores aquí
  		res.status(400).send(err);
})
const mongoose = require('mongoose');

const userSchema = mongoose.Schema({
    username : {
        type : String,
        unique : true,
        required: true,
        minLength : 2
    },
    email : {
        type : String,
        required: true,
        validate : (email) => emailValid(email)
    },
    name : String,
    tweetsIDs : [String]
})
function emailValid(email){
    return /^\S+@\S+\.\S+$/.test(email) 
}
const userModel = mongoose.model('user', userSchema)

module.exports = userModel;

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']
  }
});
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')
})

Embebed Documents


const userSchema = mongoose.Schema({
    ...
    tweetDelDia : TweetSchema,
    tweets : [TweetSchema]
})

Esto sería embebido


const userSchema = mongoose.Schema({
    ...
    tweets : [TweetSchema]
})

Ten cuidado amiguito de youtube

¿Necesitas también tener una colección de tweets?

No

Tendrás que mantener actualizados ambos tweets siempre entonces ... :S peligro

No pasa nada, es un array de objetos, de momento, solo de momento, pinta bien

Related documents

tweets : [
    {
       type : mongoose.Schema.Types.ObjectId,
       ref : 'tweet'
    }
]

Populate

tweets : [
    {
       type : mongoose.Schema.Types.ObjectId,
       ref : 'tweet'
    }
]
function getOneByUsername(req, res) {
    const { username } = req.params;
    return userModel
      .findOne({ username })
      .populate('tweets')
      .then(u => res.json(u) )
      .catch(e => res.status(500).json(e) )
}

Sin el populate

Con el populate

Triggers

 

userSchema.pre('remove', function(next) {
    TweetsModel.remove({owner: this._id}).exec();
    next();
});

Borrar los datos relacionados

Mongoose middlewares (triggers)

Projections

Dame todos los tweets (sin proyección)

 tweetsModel
  .find( {} )

Dame todos los tweets (con proyección)

 tweetsModel
  .find()
  .select({text : 1})
 tweetsModel
  .find(
     {},
     { text : 1 }
  )
 tweetsModel
  .find()
  .select("text")
 tweetsModel
  .find(
     {},
     "text"
  )

Sin proyección

Con proyección

return userModel
  .findOne({ username })
  .select({ tweets : { $slice : 3}})
  .then(u => res.json(u) )
  .catch(e => res.status(500).json(e) )

Con proyección y con populate

return userModel
  .findOne({ username })
  .select({ tweets : { $slice : 3}})
  .populate('tweets')
  .then(u => res.json(u) )
  .catch(e => res.status(500).json(e) )

EXTRA: Con proyección y con populate

return userModel
  .findOne({ username })
  .select({ tweets : { $slice : 3}})
  .populate('tweets', "text")
  .then(u => res.json(u) )
  .catch(e => res.status(500).json(e) )

EXTRA: Con proyección y con populate

return userModel
  .findOne({ username })
  .select({ tweets : { $slice : 3}})
  .populate('tweets', "text owner")
  .then(u => res.json(u) )
  .catch(e => res.status(500).json(e) )

Mongoose paginating results

MyModel.find(query, projection, { skip: 10, limit: 5 })
	.then(results => res.json(results))
	.then(err => res.status(500).json(err))
MyModel.find(query, projection, { skip: 10, limit: 5 })
	.then(results => res.json(results))
	.then(err => res.status(500).json(err))
  • skip. Sirve para indicar cuantos elementos de los resultantes tiene que "ignorar" o saltar.

 

  • limit. Indica cuantos elementos quieres como resultado.

Query Params

Pidiendo info filtrada

Pidiendo info filtrada

¿Diferencia?

Pidiendo info filtrada

Pedir información paginada

Pedir información ordenada

Responder con información ordenada

function getAll(req, res) {
  const { _skip, _limit } = req.query;
  return userModel.find()
    .limit(+_limit)
    .skip(+_skip)
    .then(async u => {
      const totalCount = await userModel.find().count();
      const totalPages = totalCount / +_limit;
      res.json({results : u, totalItems : totalCount, totalPages }) 
   })
    .catch(e => res.status(500).json(e) )
}