Hướng Dẫn Xây Dựng Books Store Với NodeJS, Express và MongoDB - Phần 3

Hướng Dẫn Xây Dựng Books Store Với NodeJS, Express và MongoDB - Phần 3

Chức Năng Hiển Thị Chi Tiết, Cập Nhật Và Xóa Author

Định Nghĩa Router Hiển Thị Chi Tiết Trong Author

Mình sẽ nói sơ qua một chút về nguyên lí hoạt động của thèn này tí nha ^^.
Để hiển thị chi tiết(show detail) author thì chỉ có cách là lấy id của author đó rồi render ra tên của author và books mà author đó đã tạo.
Thì cách hoạt động nó chỉ có vậy thôi =))
Bắt đầu code thôi nào
Trước tiên trong views có file authors/index.pug các bạn thêm cho mình dòng code này.
Đoạn code này là một thẻ liên kết a có chứa id của author mình click vao View nha.

   div
        a(href='/authors/' + author.id) View 

vào phía sau thẻ h4= author.name mà phần 1 mình đã tạo nha. Mục đích là tạo một thẻ a khi click vào đấy thì sẽ show detail author.

Controller Hiển Thị Chi Tiết Trong Author

Cũng trong file author.routes.js ta sẽ tạo một router router.get('/:id,...), trong router này sẽ là nơi mà chúng ta dùng để show detail bằng cách sử dụng id của chúng.
Trước tiên ta sẽ dựa vào id của author để lấy data của author đó ra gồm có name,... Cũng từ author đó thì ta sẽ lấy được books mà author đã tạo.

// Show Details Author
router.get('/:id', async (req, res, next) => {
  try {
    const author = await Author.findById(req.params.id) // find một author dựa vào id
    
   const booksByAuthor = await Book.find({ author: author.id }).limit(6).exec() 
   // find tất cả các book mà có author là id của author được tìm ở trên và giới hạn chỉ lấy 6 books.
    
    // Truyền data author và booksByAuthor vào trong views
    res.render('authors/show',
      {
        author: author,
        booksByAuthor: booksByAuthor
      })
  } catch{
    res.redirect('/')
  }
})

Mình sẽ thử log ra data của author và booksByAuthor cho các bạn dễ hình dung hơn nha:

Giao Diện Hiển Thị Chi Tiết Trong Author

Trong views bạn tạo cho mình thư mục là authors sẽ bao gồm 4 file index.pug, new.pug, show.pug, edit.pug.
Trước tiên các bạn nhớ extends layout vào nha sau đó chúng ta cần hiển thị tên author, nếu tác giả đó có tạo books thì ta sẽ lặp qua rồi hiển thị các thông tin của books đó và ngược lại ta sẽ hiển thị một thông báo với đoạn text "No books by author".
Câu hỏi ở đây là tại sao author ta không lặp mà books của author ta lại lặp qua? Thì đơn giản thôi khi chúng ta findById của author thì nó chỉ find một author có id mà nó đang tìm. Còn find books thì nó sẽ hiển thị toàn bộ sách mà author đã tạo để render ra views.
authors/show.pug

extends ../layouts/layout

block content
  div.container  
    h3='Author: '+ author.name
        
    if booksByAuthor.length > 0
        h2 Books By Author
        each book in booksByAuthor
            a(href='/books/' + book.id)
                img(src=book.ImageUrl, width="250" height="280")
                h3= book.title
    else
       h2 No books by author        

Và đây là kết quả khi chúng ta show detail authors nha:

Định Nghĩa Router Cập Nhật Trong Author

Thì thèn cập nhật này cách làm nó cũng tựa tựa như tạo author thôi chỉ khác là thay vì sẽ tạo một cái mới thì ta sẽ lấy id của author đó để edit lại tên author mà mình đã tạo.
Để mà show detail được chúng ta phải dựa vào id của author, thì update author cũng tương tự vậy thôi nha.
Bắt đầu code thôi nào
Trước tiên trong views có file authors/index.pug các bạn thêm cho mình dòng code này.
Đoạn code này là một thẻ liên kết a có chứa id của author khi mình click sẽ vào trang edit author nha.

   a(href='/authors/' + author.id + '/edit') Edit

vào phía sau thẻ a(href='/authors/' + author.id) View mà ở trên mình đã tạo nha.

Cũng tương tự như ở trên trong views có file authors/show.pug các bạn thêm cho mình dòng code này nha.

   div
      a(href='/authors/'+author.id +'/edit') Edit

vào phía sau thẻ h2='Author: '+ author.name mà ở trên mình đã tạo nha.

Trong thư mục routes có file author.routes.js các bạn tạo cho mình hai router update đó là router.get('/:id/edit',...) để render ra views và router.put('/:id',...) để cập nhật author nha. Trước mắt các bạn chỉ cần tạo và render ra views trước cho mình là được.

// Get Update Author
router.get('/:id/edit', async (req, res, next) => {
  try {
    const author = await Author.findById(req.params.id) // find ra một author dựa vào id
    res.render('authors/edit', { author: author }) // Truyền data vào trong views
  } catch{
    res.redirect('/authors')
  }
})
  
// Update Author  
router.put('/:id', async (req, res, next) => {
   
 });

Giao Diện Cập Nhật Trong Author

Trong views bạn tạo cho mình thư mục là authors sẽ bao gồm 4 file index.pug, new.pug, show.pug, edit.pug.
Trước tiên các bạn nhớ extends layout và tạo cho mình một cái form giống như tạo author nhưng các bạn phải thay đổi lại đường dẫn action nha.
authors/edit.pug


block content
  .container
    h2 Edit Author
    form(method="POST" action="/authors/" + author.id)
      label(for="name") Name
      input(placeholder="Name", type="text" name="name" value=author.name)
      div
       a(href="/authors/" + author.id) Cancel
       button(type="submit") Update

Và đây là giao diện để cập nhật author:

Controller Cập Nhật Trong Author

Để có thể cập nhật được author và sử dụng được phương thức PUT ta cần sử dụng một middleware đó là method override.
Module method override này mình đã khai báo và kích hoạt trong file app.js ở phần 1 rồi nha.
Vì biểu mẫu html nó chỉ hỗ trợ hai phương thức là GETPOST mà thôi, nên để có thể sử dụng được phương thức PUT thì ta cần phải ghi đè chúng.

Trong views có file authors/edit.pug các bạn thêm cho mình đoạn mã này "?_method=PUT" vào trong action của form giúp mình với nha.
Phương thức Put ghi đè lên phương thức Post
form(method="POST" action="/authors/" + author.id + "?_method=PUT")

Kiểm tra tên của author khi được cập nhật
Thì phần này nó cũng giống như tạo books ở phần 1, ta sẽ check xem tên cập nhật có trùng với tên mà author đã tạo từ trước nếu trùng thì sẽ hiển thị thông báo.
Bởi vì tên tác giả của một cuốn sách nào đó thì rất hiếm khi trùng nhau có thể nói là không có, còn tên người mà trùng nhau là chuyện bình thường.

Trong file authors.routes.js ta có router.put('/:id',...) dùng để update author.

// Update Author

router.put('/:id', async (req, res, next) => {
  let author
  
  author = await Author.findById(req.params.id)// Tìm một author dựa vào id

  // Kiểm tra name author có tồn tại hay không
  const nameAuthor = await Author.findOne({ // Tìm một tên author trong DB với tên được cập nhật
    name: req.body.name
  });
    // Nếu tìm được tên trùng nhau sẽ render ra mess
  if (nameAuthor) {
    return res.render('authors/edit', {
      author: author,
      errorMessage: 'Author Already Exists'
    })
  }
  
  try {
    author.name = req.body.name  // Cập nhật tên của author đó
    await author.save(); // Lưu tên đã được cập nhật vào DB
    res.redirect(`/authors/${author.id}`)  // Chuyển trang show detail nếu cập nhật thành công

  } catch{
    // Ngược lại, nếu cập nhật thất bại thì sẽ thông báo lỗi
    if (author == null) {  
      res.redirect('/')
    } else {
      res.render('authors/edit', {
        author: author,
        errorMessage: 'Error Updating Author'
      })
    }
  }
})

Nếu mà có lỗi khi cập nhật author thì trong authors/edit.pug các bạn thêm dòng code ở dưới vào phía thẻ h2 Edit Author để hiển thị thông báo lỗi nha.

    if locals.errorMessage
        = errorMessage

Và đây là kết quả khi chúng ta cập nhật author:

Định Nghĩa Router Xóa Trong Author

Trước tiên trong views có file authors/index.pug các bạn thêm cho mình dòng code này.
Đoạn code này là một thẻ liên kết a có chứa id của author khi mình click sẽ xóa author nha.

 form(method="POST" action="/authors/" + author.id )
           button(type='submit') Delete

vào phía sau thẻ a(href='/authors/' + author.id + '/edit') Edit mà ở trên mình đã tạo nha.

Cũng tương tự như ở trên trong views có file authors/show.pug các bạn thêm cho mình dòng code này nha.

form(method="POST" action="/authors/" + author.id )
              button(type='submit') Delete      

vào phía sau thẻ a(href='/authors/'+author.id +'/edit') Edit mà ở trên mình đã tạo nha.

Controller Xóa Author

Cũng trong file authors.routes.js ta có router.delete('/:id',...) dùng để xóa author.

// Delete Author
router.delete('/:id', async (req, res, next) => {
 let author
  try {
   author = await Author.findById(req.params.id)// find id author cần xóa
    await author.remove() // Xóa author
    res.redirect('/authors') // Chuyển hướng sang trang /authors nếu xóa được
  } catch{

    res.redirect(`/authors/${author.id}`)

  }
})

Để xóa được author thì cũng giống như cập nhật author nên mình sẽ không giải thích gì nhiều nữa. Muốn sử dụng được phương thức Delete ta cần sử dụng một middleware đó là method override.

Trong views có file authors/index.pugauthors/show.pug các bạn thêm cho mình đoạn mã này "?_method=DELETE" vào trong action của form giúp mình với nha.
Phương thức Delete ghi đè lên phương thức Post
form(method="POST" action="/authors/" + author.id + "?_method=DELETE")

Nhưng chờ chút còn một vấn đề nữa, chưa xong mô nạ :))
Có một vấn đề như này nếu như mà xóa theo cách trên í thì chỉ xóa author mà thôi còn books mà author tạo sẽ không bị xóa. Bởi vậy nên khi chúng ta xóa author thì cần phải xóa luôn books mà author đó đã tạo đúng không nào, mình đã có cách giải quyết chúng hehehe.

Để khi xóa author thì books cũng được xóa luôn ta cần sử dụng một middleware mà mongoose cung cấp đó là Pre.
Pre middleware này sẽ thực hiện một method xóa trước một hành động xóa xảy ra, có nghĩa là nó xóa books trước khi xóa author nói như vậy cho nó dễ hiểu nha. Còn sử dụng trong trường hợp nào thì các bạn tìm hiểu thêm tại đây nha.
Trong models có file author.model.js đây là nơi chứa các schema của author. Trước tiên bạn cần require cho mình model Book và sau đó gọi pre middleware function.
Khi bạn gọi next() thì nó sẽ được chuyển sang middleware tiếp theo nhưng mà vẫn chạy câu lệnh bên dưới middleware hiện tại.
Những trường hợp nào mà bạn sử dụng dụng Pre

const Book = require('../models/books.model')

authorSchema.pre('remove', function(next) {
    // Sẽ tìm những books có id author mà bạn xóa
    Book.find({author: this.id}, (err, books) => {
        if (err) {
            next (err)
        } else if (books.length > 0) { // Nếu author đó có tạo books thì sẽ xóa toàn bộ books
            books.forEach(book => book.remove())
            next()
        } else {
            next()
        }
    })
})

Và đây là kết quả khi chúng ta thực hiện xóa author nha:

Vậy là kết thúc phần 3 rồi nha mời các bạn xem tiếp phần 4
To Be Continued !!

Lời Kết

Vậy là xong bài Hướng Dẫn Xây Dựng Books Store Với NodeJS, Express và MongoDB - Phần 3 rồi nhé. Các bạn xem phần tiếp theo nha mình chia thành nhiều phần ra để các bạn có thể đọc và làm một cách dễ dàng hơn rành mạch và dễ hiểu hơn.
Các bạn nhớ like và theo dõi fanpage Thanh Long Dev để nhận những thông báo về bài viết mới nhất nha.
Nếu các bạn cảm thấy bài viết của mình hay thì các bạn có thể ủng hộ mình để mình có thêm động lực để ra những bài topic hay và chất lượng hơn ủng hộ mình tại đây nha.
Chúc Các Bạn Thành Công!!