constexpress=require('express')constrouter=express.Router()const {getAllBooks,getBookById,createABook,updateABook} =require('../controllers/book')router.get('/', getAllBooks)router.get('/:id', getBookById)router.post('/', createABook)// This is the new route we are adding inrouter.put('/:id', updateABook)
Let's also create our controller so we can fill in the details later on.
File: src/controllers/book/updateABook.js
constcatchException=require('../../utils/catchExceptions')/** * Updates an existing book listing by id. */constupdateABook=catchException(async (req, res) => {// we'll fill in the details after we get each of the other layers ready})module.exports= updateABook
2 - Input Request
For the input request, this will seem very familiar. It's because this is the exact code we used for our createBookRequestDto.js with the exception of an additional id field.
constApiException=require('../utils/ApiException')constfields= ['id','title','description','price','author','datePublished']constupdateBookRequestDto= (data) => {consterrors= []fields.forEach((field) => {if (!(field in data)) {errors.push(`This DTO's property is required: ${field}.`) } })if (errors.length>0) {thrownewApiException({ status:'error', code:422, message:'Input fields are of not the correct form.', data:null, errors }) }return data}module.exports= updateBookRequestDto
3 - Middleware
File: src/routes/book.route.js
// We'll be adding in the isAuthenticated middleware and also our// new middleware, the bookPermission middlewarerouter.put('/:id', isAuthenticated, bookPermission, updateABook)
As stated previously, we need a way to check if the request book to be updated does indeed belong to the currently authenticated user.
This will help us do so. Notice also that we are going to be using the bookService.getBookId method we created previously. This is a great functionality to reuse because the getBookById inherently has a check to see if the bookId is correct, otherwise it will throw an exception.
File: src/middleware/bookPermission.middleware.js
constglobalResponseDto=require('../responses/globalResponseDto')constcatchExceptions=require('../utils/catchExceptions')constbookService=require('../domain/services/book.service')constbookPermission=catchExceptions(async (req, res, next) => {// When updating or deleting a book, the book must belong to the user that created itconstbookId=req.params.idconstbook=awaitbookService.getBookById(bookId)// check to see if the current authenticated user's the owner of the// requested bookIdif (req.session.user._id !==book.userId.toString()) {res.status(401).json(globalResponseDto({ status:'error', code:401, message:'Access denied: you must be the owner of this book when updating or deleting it.', data:null, errors: ['Access denied: you must be the owner of this book when updating or deleting it.' ] }) ) }next()})module.exports= bookPermission
4 - Validation
Again, this is very similar to the createBookValidator with the exception of an additional id field. You are probably wondering why we require all fields instead of a subset of them. Do recall that this is a PUT request, which means we must accept the entire resource into our API as we are re-updating the entire entity in database.
Yet again, this is similar to what we had before when we wrote the create method in our bookRepository and the createBook method in our bookService. We now do this for updating a book.
constbookRepository=require('../repositories/book.repository')// Update a bookconstupdateBookById=async (book) => {constnewlyUpdatedBook=awaitbookRepository.updateById(book.id, book)return newlyUpdatedBook}
6 - Events
None.
7 - Response
Now for the response.
Do note that our isAuthenticated and bookPermission middleware will handle most of the error messages for us.
For our success message we will put have the following in our controller.
File: src/controllers/book/updateABook.js
constcatchException=require('../../utils/catchExceptions')constglobalResponseDto=require('../../responses/globalResponseDto')constupdateBookRequestDto=require('../../requests/updateBookRequestDto')constupdateBookValidator=require('../../validators/updateBookValidator')constbookService=require('../../domain/services/book.service')constbookResponseDto=require('../../responses/bookResponseDto')/** * Updates an existing book listing by id. */constupdateABook=catchException(async (req, res) => {constupdateBookFields=updateBookRequestDto({ id:req.params.id,...req.body })updateBookValidator(updateBookFields)constupdatedBook=awaitbookService.updateBookById(updateBookFields)res.json(globalResponseDto({ status:'success', code:200, message:`The book has successfully been updated.`, data:bookResponseDto(updatedBook), errors:null }) )})module.exports= updateABook
Once again, this is nice and thin, and easy on the eyes.