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

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

Giới Thiệu Chung

Hôm nay mình sẽ hướng dẫn các bạn xây dựng một project books store với NodeJS, Express và MongoDB.
Mục đích của bài này là mình mong muốn giúp các bạn nắm và ôn lại các kiến thức mà mình được học thông qua các project căn bản.
Mình sẽ viết một cách tận tình và chu đáo mong các bạn xem đầy đủ và kỹ càng. Thế là mình vui lắm rồi =))

Bắt Đầu Làm Thôi Nào

Lên Ý Tưởng

Thì ý tưởng của bài này cũng dựa theo các phương thức Rest để mà làm thôi đó là Get, Post, Put và Delete,... vừa giúp các bạn học thêm cũng như ôn lại các kiến thức mình được học.
Books Store gồm hai phần chính đó là books và author.
Project của chúng ta sẽ gồm các chức năng như sau: tạo/xem/sửa/xóa/tìm kiếm,.. books và author.
Và để biết nó được làm như thế nào thì các bạn cùng xem tiếp phía dưới nha :))

Cài Đặt Và Thiết Lập

Trước tiên các bạn tạo cho mình một folder trong folder đó là nơi chứa các thư mục dùng để mình code project library books.
Để mà cài các module trước hết các bạn phải cài đặt NodeJS tại đây.
Khởi tạo ứng dụng với file package.json
Trong thư mục gốc của ứng dụng của bạn và nhập npm init để khởi tạo ứng dụng của bạn với tệp package.json.
npm init
Sau đó các bạn cài đặt các module ở dưới để thiết lập ứng dụng nha.

  • npm install express --save
    Module dùng để cài đặt framework express của nodejs giúp các bạn code một cách tối giản cũng như nhanh chóng hơn.
  • npm install body-parse --save
    Body-parser là một middleware dùng để xử lý dữ liệu các HTTP POST request
  • npm install pug --save
    Pug nó chỉ là một template engine mà thôi.
  • npm install cloudinary --save
    Cloudinary là một dịch vụ đám mây cung cấp giải pháp cho toàn bộ quy trình quản lý hình ảnh của ứng dụng web.
  • npm install multer --save
    Multer là một middleware xử lý các dữ liệu multipart/form-data chủ yếu là xử lý tệp thôi.
  • npm install method-override --save
    Khi chúng ta sử dụng phương thức PUT và DELETE trong HTML thì gặp sự cố. Các trình duyệt hỗ trợ PUT và DELETE nhưng nó chỉ bằng cách sử dụng yêu cầu qua AJAX, chứ không phải thông qua gửi 'biểu mẫu HTML'.
    Đó cũng là lý do tại sao chúng ta lại sử dụng method-override một middleware để 'ghi đè phương thức' để giải quyết vấn đề này.
    Nếu các bạn muốn hiểu rõ hơn thì tìm hiểu thêm tại đây nha.
  • npm install nodemon --save
    Tự động reload lại server khi bạn thay đổi code. Để khởi chạy server các bạn thêm "dev": "nodemon src/app.js" vào file** package.json** nha. Các bạn mở terminal lên sau đó gõ npm run dev để khởi chạy server nha.
  • npm install mongoose --save
    Mongoose là một Object Document Mapper (ODM). Điều này có nghĩa là Mongoose cho phép bạn định nghĩa các object (đối tượng) với một schema được định nghĩa rõ ràng.
  • npm install dotenv --save
    Dotenv là một biến môi trường dùng để bảo mật các thông tin quan trọng như username, password, url database,...Nếu chúng ta không lưu những thông tin mật vào file .env thì khi push source code lên github thì ai cũng có thể vào xem được và ai cũng biết username, password thì sẽ bị người khác chiếm đoạt và đánh cắp tài liệu rất là nguy hiểm nha.
    Và đây cũng là các package mà mình khi đã cài đặt cũng như cấu hình xong.

Cấu Trúc Thư Mục Cho Dự Án

Sau khi hoàn thành việc cài đặt module các bạn tạo cho mình một thư mục src, thư mục này dùng để chứa tất cả các file trong quá trình mình code project.

Lưu ý: Mình khuyên các bạn nên tạo thêm một folder controllers và file để chứa các logic khi mình code nha. Do mình code để làm demo cho mọi người nên mình viết chung vào thằng routes luôn.

Bắt Đầu Code Thôi Nào

Thiết Lập Web Server

Trong thư mục gốc các bạn tạo cho mình file app.js, file này là file chính dùng để điều khiển mọi hoạt động chính của server.
Trong file này các bạn cần import và enable các router homepage, authors và books vào nha.
Và cũng khai báo và enable luôn method-override và body-parser vào luôn nha.

const express = require("express")
const app = express()
const bodyParser = require('body-parser')
const methodOverride = require('method-override')
const port = 4444

// Import routes
const IndexRouter = require("../src/routes/index.routes")
const BookRouter = require("../src/routes/books.routes")
const AuthorRouter = require("../src/routes/author.routes")

// view engine setup
app.set('view engine', 'pug');
app.set('views', 'src/views');

// Enable Static file
app.use(express.static('src/public'))

// Enable Method Override
app.use(methodOverride('_method'));

// Enable Body Parser
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ limit:'10mb' ,extended: false }));  

// Enable Routes
app.use('/', IndexRouter)
app.use('/books', BookRouter)
app.use('/authors', AuthorRouter)

app.listen(port, () => {
    console.log(`Server listening ${port}`)
})

Trong thư mục routes có file index.routes.js, các bạn tạo cho nó một Route handlers và thử chạy sever xem sau. Mục đích để các bạn test xem server có chạy không nha.

router.get('/', (req, res, next) => {
   res.send('Hello Everyone')
})

Thiết Lập Database

Để mà có thể thiết lập được database thì các bạn đăng nhập tài khoản mongodb atlas tại đây nha. Sau khi các bạn bạn đã có tài khoản mongodb atlas thì tiếp đến cài đặt cấu hình để có thể kết nối mongodb atlas với ứng dụng của chúng ta. Để biết thêm chi tiết các bạn xem bước 3 trong bài viết của mình hướng dẫn deploy project nodejs lên heroku tại đây nha.
Sau đó các bạn copy link database mongodb đã tạo rồi paste vào project của mình là được nha. Các bạn tạo cho mình một file .env bên trong thư mục gốc.
Trong file này các bạn tạo một đường dẫn là DATABASE_URL=mongodb+srv://long:<password>@cluster0-b7d8c.mongodb.net/test?retryWrites=true&w=majority
Trong file app.js các bạn khai báo module dotenv với module mongoose và tạo đường dẫn mongodb.

// Khai báo dotenv
require('dotenv').config()

// Khai báo mongoose
var mongoose = require('mongoose')

// Connect database
 mongoose.connect(process.env.DATABASE_URL,{useUnifiedTopology: true,
   useNewUrlParser: true, })
 const db = mongoose.connection
  db.on('error', error => console.error(error))
  db.once('open', () => console.log('Connected to Database'))

Để biết nó đã kết nối hay chưa các bạn khởi động lại server sẽ biết nha.

Thiết Kế Layout Cho Project

Trong phần cấu trúc thư mục có folder views trong đó bạn tạo cho mình 2 thư mục layouts gồm có file layout.pugpartials gồm có header.pug. Hai thư mục này sẽ nơi chứa bố cục của ứng dụng.
partials/header.pug

header
  .container
    .row
      nav.navbar.navbar-expand-lg.navbar-light
        a.navbar-brand(href='/')
          img.img-fluid(src='https://png.pngtree.com/templates_detail/20180917/book-library-education-logo-png_32062.jpg', width='50')
        button.navbar-toggler(type='button', data-toggle='collapse', data-target='#navbarNavDropdown', aria-controls='navbarNavDropdown', aria-expanded='false', aria-label='Toggle navigation')
             span.navbar-toggler-icon
        #navbarNavDropdown.collapse.navbar-collapse.justify-content-end
          ul.navbar-nav.justify-content-end
            li
              a.nav-link(href="/authors") Authors
            li
              a.nav-link(href="/authors/new") Add Author
            li
              a.nav-link(href="/books") Books
            li
              a.nav-link(href="/books/new") Add Book


layouts/layout.pug
Trong này bạn chỉ cần include header và liên kết các link bootstrap vào nha

doctype html
html
  head
    title Books Store
    meta(charset='utf-8')
    meta(name='viewport', content='width=device-width, initial-scale=1')
    link(rel='stylesheet', href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css')
    link(rel='stylesheet', href='/stylesheets/style.css')
  body
    include ../partials/header.pug
    block content
    
    script(src='https://code.jquery.com/jquery-3.4.1.min.js', integrity='sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=', crossorigin='anonymous')
    script(src='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js', integrity='sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM', crossorigin='anonymous')

Chức Năng Hiển Thị Và Tạo Mới Author

Định Nghĩa Router Tạo Mới Trong Author

Trong thư mục routes có file author.routes.js thì trong này sẽ là nơi chứa toàn bộ các source liên quan đến author.
Trước tiên các bạn tạo cho mình hai router tạo mới đó là router.get('/new',...)router.post('/',...) trước đã nha. Các bạn chỉ cần tạo và render ra views trước cho mình là được.

const express = require("express")
const router = express.Router()

// Get New Author
router.get('/new', (req, res) => {
  res.render('authors/new', {
    author: new Author()
  });
});
  
// Create new author  
router.post('/', async (req, res) => {
   
 });

Giao Diện Tạo Mới 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
authors/new.pug

extends ../layouts/layout

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

Và đây là giao diện căn bản để tạo author:

Định Nghĩa Các Schema Author Cho Mongoose

Như các bạn đã đọc ở trên thì cấu trúc thư mục cho dự án bên trong thư mục src các bạn tạo cho mình thư mục models, bên trong thư mục models các bạn tạo cho mình file author.model.js.
Trong file author.model.js cần tạo một đối tượng book và sẽ chứa fields name author.

const mongoose = require('mongoose');

const authorSchema = new mongoose.Schema({
    name:{type: String, required:true}
})

module.exports = mongoose.model('Author', authorSchema)

Tham số thứ nhất là tên riêng cho collection sắp được tạo ra cho mô hình của bạn, và tham số thứ hai là schema mà bạn muốn dùng để tạo ra mô hình.

Controller Tạo Mới Trong Author

Cũng trong file authors.routes.js ta có router.post('/',...) dùng để tạo author. Các bạn nhớ require model của author mà mình đã tạo vào nha.

Kiểm tra tên của author khi được tạo
Ta sẽ check xem tên được tạo có trùng với tên mà author đã tạo từ trước hay không 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 có thể nói là không bao giờ có tên tác giả giống, còn tên người mà trùng nhau là chuyện bình thường. Vì thế nên ta cần phải check name của tác giả

Nó sẽ tạo một object rộng chứa dữ liệu author được truyền vào. Rồi lưu dữ liệu vào database đã được khởi tạo. Chúng ta sử dụng try{} catch{} nên khi có lỗi thì sẽ render ra lỗi khi tạo author.

const Author = require('../models/author.model')

// Create New Author  
router.post('/', async (req, res) => {
  const author = new Author({ name: req.body.name })
  
  // Kiểm tra name author có tồn tại hay không
  // Tìm một tên author trong DB với tên được tạo
  const nameAuthor = await Author.findOne({ name: req.body.name });
  
  // Nếu tìm được tên trùng nhau sẽ render ra mess
  if (nameAuthor) {
    return res.render('authors/new', {
      author: author,
      errorMessage: 'Author Already Exists'
    })
  }

  try {
    await author.save();
    res.redirect('/authors')
  } catch {
    res.render('authors/new', {
      author: author,
      errorMessage: 'Error Creating Author'
    })
  }

});

Khi mình tạo author thành công thì lưu vào DB và redirect sang trang authors, bây giờ trong router.get('/',..) các bạn res.send một tin nhắn gì đấy khi tạo author thành công chẳng hạn.

router.get('/', async (req, res) => {
  res.send('Hello Authors')  
})

Nếu mà có lỗi khi tạo author thì trong authors/new.pug các bạn thêm dòng code ở dưới vào phía sau thẻ h2 New Author là được nha.

    if locals.errorMessage
        = errorMessage
        

Và đây là kết quả khi chúng ta tạo author nha:

Định Nghĩa Và Controller Router Hiển Thị Trong Author

Trong thư mục routes có file author.routes.js thì trong này sẽ là nơi chứa toàn bộ các source code liên quan đến author.
Chúng ta tạo router.get('/') đây chính là router của trang authors, mục đích của trang này là tìm kiếm và hiển thị author.

Thì trước tiên các bạn viết cho mình hàm try{}catch{}, trong try{} là nơi mà ta sẽ find Author trong DB và render ra author còn catch{} sẽ redirect sang trang homepage nếu có lỗi xảy ra.
Còn về vấn đề tìm kiếm thì mình sẽ sử dụng req.query để truy vấn dữ liệu nha kết hợp với RegExp.
RegExp là một chuỗi các ký tự tạo thành một mẫu tìm kiếm. Mẫu tìm kiếm có thể được sử dụng cho các hoạt động tìm kiếm văn bản và thay thế văn bản.

Mình sẽ giải thích sơ qua chút về cách hoạt động của chức năng tìm kiếm. Tạo một object rỗng là nơi chứa các cặp key và value, key ở đây là name và value là biểu thức chính quy vd: {name: /long/i}.
Toán tử && sẽ trả về truthy khi tất cả các toán hạng đều là true có nghĩa là mình search có name của author thì điều kiện nó là true.

const Author = require('../models/author.model')

// GET Authors Page
router.get('/', async (req, res) => {
   let searchOptions = {}
   if (req.query.name != null && req.query.name !==''){
     searchOptions.name = new RegExp(req.query.name, 'i') 
     // 'i' ở đây có nghĩa là tìm kiếm tên mà không phần biệt chữ hoa hay thường
   }
   try{
     const authors = await Author.find(searchOptions);
     res.render('authors/index',{
       authors: authors,
       searchOptions : req.query
     })
   
   }catch{
     res.redirect('/')
   }
 
});

Giao Diện Hiển Thị 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 và lặp qua các author để hiển thị ra tên của author. Tạo một form để làm ô tìm kiếm cho author với method="Get" nha.
searchOptions này là một object do mình đã định nghĩa req.query ở trên bây giờ muốn lấy value thì ta cần .name để có thể lấy được value của chúng.
authors/index.pug

extends ../layouts/layout

block content
 .container
    h2 Search Authors
    form(action="/authors" method="GET")
        input( type="text" name="name" placeholder="Name Author" value=searchOptions.name)
        button(type="submit") Search
    each author in authors
      h4= author.name

Lưu ý: Mình có cập nhật lại code tại một số chỗ nên khi bạn thấy trên ảnh gif sẽ không được chính xác cho lắm nha.
Và đây là kết quả khi chúng ta hiển thị và tìm kiếm author nha:

Vậy là kết thúc phần 1 rồi nha mời các bạn xem tiếp phần 2
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 1 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!!