Completando las rutas faltates

Nos estaría faltando implementar las siguientes rutas

  • GET /noticias/:id
  • POST /noticias/:id/comentarios
  • PUT /noticias/:id/upvote
  • PUT /noticias/:id/comentarios/:idComentario/upvote

URL parameters

Comenzaremos con la primera GET /noticias/:id la cual debe recuperar una noticia desde mongo por un id especificado. Por ejemplo: Al realizar el usuario el pedido GET /noticias/42 deberá devolversele la noticia con id 42.

Agregaremos el cuerpo para la ruta en nuestro routes.js

src/backend/routes/routes.js

router.get('/noticias/:id', (req, res, next) => { Post.findById(req.params.id) .then(noticia => res.json(noticia)) .catch(next) })

El objeto req.params contiene una asociación de parametros de la ruta y los valores proporcionados para los mismos. req.params.id contendrá entonces el valor del paraemtro :id proporcionado por el cliente.

Notaremos que para probar dicha ruta es necesario contar con el id de una ruta previamente persistida. Dicho id es autogenerado por mongodb al momento de persistir un nuevo objeto. Podemos usar Robomongo para acceder a la información persistida en la base y obtener un id valido, aunque también es buena idea modificar la anteriormente creada ruta POST /noticias para que devuelva el id con el que una noticia recientemente persistida en mongo.

Podemos lograr ese efecto con un sencillo cambio:

src/backend/routes/routes.js

router.post('/noticias', (req, res, next) => { const noticia = new Post(req.body) noticia.save() .then(noticia => res.json(noticia.id)) .catch(next) })

Entonces, probar la nueva ruta con curl es un asunto sencillo. Primero creamos una nueva noticia:

$ curl -X POST \
       -H 'Content-Type: application/json' \
       -d '{ "title": "Noticia 1", "content": "lorem ipsum dolor sic" }' \
       localhost:3001/noticias 

["58b9be6f1a88980726a83c4d"]

Luego utilizamos la nueva ruta para recuperarla.

$ curl -X GET localhost:3001/noticias/58b9be6f1a88980726a83c4d

{"_id":"58b9be6f1a88980726a83c4d","title":"Noticia aaa999","content":"lorem ipsum dolor sic","__v":0,"createdAt":"2017-03-03T19:05:19.116Z","upvotes":0}

Exito!

Upvote

Proseguimos con la siguiente ruta (upvote).

src/backend/routes/routes.js

router.put('/noticias/:id/upvote', (req, res, next) => { Post.findById(req.params.id) .then(noticia => { if (! noticia ) { throw new Error(`Noticia no encontrada ${req.params.id}`) } noticia.upvotes ++; let savePromise = noticia.save() savePromise.then(noticia => res.json(noticia)) .catch(next) }) .catch(next) })

A priori la implementación parece un tanto sencilla:

  1. Primero recuperamos la noticia para el id que nos ha sido provisto.
  2. Luego incrementamos la cantidad de votosde la misma.
  3. La guardamos en mongo. Hacemos esto por medio del mensaje save. Esto es una operación asincrónica y como no queremos responder al cliente hasta que el guardado de la noticia haya sido completado correctamente (esto es una decisión de diseño) solo invocaremos a res.json(...) una vez que la promise devuelta por el método save haya sido resuelta (o sea, en el then de dicha promise).
  4. Desde luego no podemos olvidarnos de manejar los errores que puedan ocurrir durante el guardado asincrónico. Para eso usamos el catch con la misma estrategia que antes (delegar en la función then para que express maneje el error).

Mmmm. Algo huele feo.

Algo de código repetido

Como podemos notar tanto la ruta GET /noticias/:id como POST /noticias/:id/upvote contienen exactamente la misma lógica para recuperar una noticia por id desde la base de datos. Este código repetido de alguna forma debería ser consolidado.

Express.js nos provee una funcionalidad llamada params por medio de la cuál podemos preprocesar parametros antes de la invocación a nuestras rutas.

src/backend/routes/routes.js

router.param('noticia', (req, res, next, value) => { Post.findById(value) .then(noticia => { if (! noticia ) { throw new Error(`Noticia no encontrada ${value}`) } req.noticia = noticia next() }) .catch(next) })

Como podemos instruimos a express de preprocesar un parámetro llamado noticia. Buscamos en mongo de la misma forma que lo veníamos haciendo con anterioridad, si encontramos un objeto guardamos el mismo en req.noticia. Ahora solo resta modificar el código de nuestras rutas.

src/backend/routes/routes.js

router.get('/noticias/:noticia', (req, res, next) => { res.json(req.noticia) }) router.put('/noticias/:noticia/upvote', (req, res, next) => { const noticia = req.noticia noticia.upvotes++ noticia.save() .then(noticiaGuardada => res.json(noticiaGuardada)) .catch(next) })

Nombres

Es util notar que cambiamos el nombre del parametro de :id a :noticia. Esto fue hecho así porque el nombre id carecía de información fuera del contexto de la ruta de noticia.

Upvote - lógica de negocio

Si bien la lógica de qué implica hacer upvote a una ruta es hoy por hoy tan sencilla como hacer noticia.upvotes++ es quizás una buena decisión de diseño extraer dicha lógica fuera del archivo de rutas y delegarla en el objeto noticia en si mismo

src/backend/routes/routes.js

router.post('/noticias/:noticia/upvote', (req, res, next) => { const noticia = req.noticia noticia.upvote() noticia.save() .then(noticiaGuardada => res.json(noticiaGuardada)) .catch(next) })

Para eso agregaremos el método upvote al schema de noticia que habíamos definido antes.

src/backend/models/Post.js

... postSchema.methods.upvote = function() { this.upvotes++ } const Post = mongoose.model('Post', postSchema) ...

A continuación probamos que todo esté andando correctamente.

$ curl -X PUT localhost:3001/noticias/58b9be6f1a88980726a83c4d/upvote

{"_id":"58b9be6f1a88980726a83c4d","title":"Noticia aaa999","content":"lorem ipsum dolor sic","__v":0,"createdAt":"2017-03-03T19:05:19.116Z","upvotes":1}

GitHub

Como referencia dejamos el estado del proyecto en GitHub

results matching ""

    No results matching ""