# Một số khái niệm ### JavaScript - NodeJS - Express được hiểu là **Ngôn ngữ - Nền tảng - Framework**, tức là: Express một framework để xây dựng web server sử dụng ngôn ngữ là JavaScript, được chạy trên nền tảng NodeJS ### Express.js Routing - Trong Express.js, requests từ người dùng được xử lý thông qua các tuyến đường (routes). - **Routing** là quá trình xác định cách ứng dụng *phản hồi* yêu cầu của người dùng tới các đường dẫn (URL) cụ thể - Routing cho phép tạo các tuyến đường tới các trang, xử lý các phương thức HTTP như GET, POST, PUT, DELETE, và thực hiện các xử lý phù hợp dựa trên các yêu cầu từ người dùng. - Express.js cung cấp các phương thức để định nghĩa tuyến đường và xử lý yêu cầu, ví dụ: - `app.get(path, callback)`: Xử lý yêu cầu HTTP GET tới đường dẫn cụ thể. - `app.post(path, callback)`: Xử lý yêu cầu HTTP POST tới đường dẫn cụ thể ### GET Request - Trong hàm xử lý của tuyến đường GET, bạn có thể truy cập các thông tin request thông qua đối tượng `req`, và gửi response thông qua đối tượng `res`. - Các phương thức phổ biến của đối tượng Response trong Express.js bao gồm: - `res.send()`: Gửi một phản hồi văn bản đơn giản. - `res.json()`: Gửi một phản hồi dưới dạng JSON. - `res.render()`: Gửi một phản hồi bằng cách sử dụng template engine để render nội dung HTML. - `res.redirect()`: Chuyển hướng yêu cầu tới một địa chỉ URL khác. - `res.sendFile()`: Gửi một tệp tin tĩnh từ máy chủ tới người dùng. - Trong Express.js, bạn có thể xử lý tham số truy vấn trong yêu cầu GET thông qua đối tượng **`req.query`** > Tham số truy vấn là các thông tin bổ sung được gửi từ máy khách (client) thông qua URL sau dấu “?” và được phân tách bằng dấu “&”. ví dụ nếu URL yêu cầu GET có dạng `/users?name=tra&age=25`, thì `req.query.name` sẽ chứa giá trị “John” và `req.query.age` sẽ chứa giá trị 25. ### POST Request >Khi gửi một POST Request, client đóng gói dữ liệu vào phần body của yêu cầu và gửi đến một địa chỉ URL cụ thể trên server. - Để xử lý dữ liệu POST trong Express.js, bạn cần sử dụng một **middleware** (phần mền trung gian) như **body-parser** để phân tích dữ liệu POST từ phần body của Request và chuyển nó thành các đối tượng JavaScript, để phân tích và truy xuất dữ liệu từ phần body của POST Request. - Phần mềm trung gian là các chức năng chặn các trình xử lý tuyến đường, thêm một số loại thông tin. - Một phần mềm trung gian cần được gắn kết bằng phương thức `app.use(path, middlewareFunction)`. Đối số đầu tiên path là tùy chọn, nếu bỏ qu thì phần mềm trung gian sẽ được thực thi cho tất cả các yêu cầu. - Để sử dụng body-parser, cài đặt và import module như sau: ```javascript const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); ``` - Trong hàm callback của route POST, để truy cập dữ liệu được gửi từ client thì ta thông qua đối tượng **`req.body`** ### MongoDB and Mongoose #### MongoDB - MongoDB là một ứng dụng cơ sở dữ liệu lưu trữ các JSON documents (or records) - Thay vì sử dụng các *table* và *row* như trong cơ sở dữ liệu SQL, MongoDB là cơ sở dữ liệu NoSQL, được tạo thành từ **collection** và **document**. - **Document** được tạo thành từ các cặp field-value. Document mang vai trò tương tự như *row* trong các hệ thống cơ sở dữ liệu quan hệ truyền thống. - **Collection** là một tập hợp các document MongoDB, tương đương với *table* trong SQL, là nơi chứa các bộ document. #### Mongoose là gói npm phổ biến để tương tác với MongoDB. # Thực hành Projects ## 1. Timestamp Microservice ### Yêu cầu ![image](https://hackmd.io/_uploads/H1NFOAveA.png) ### Solution để giải quyết bài này, ta cần các hàm xử lý ngày tháng trong Javascript `new Date().getTime()` để chuyển ngày tháng sang dạng thời gian unix `new Date().toUTCString()` để về ngày tháng về dạng thời gian theo utc file **index.js** ```javascript // index.js // where your node app starts // init project var express = require('express'); var app = express(); // enable CORS (https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) // so that your API is remotely testable by FCC var cors = require('cors'); app.use(cors({optionsSuccessStatus: 200})); // some legacy browsers choke on 204 // http://expressjs.com/en/starter/static-files.html app.use(express.static('public')); // http://expressjs.com/en/starter/basic-routing.html app.get("/", function (req, res) { res.sendFile(__dirname + '/views/index.html'); }); // tạo hàm kiểm tra xem ngày có hợp lệ hay không const isInvalidDate = (date) => date.toUTCString() === "Invalid Date" // your first API endpoint... app.get("/api/:date", function (req, res) { let date = new Date(+req.params.date); console.log(date) // trường hợp lấy date là thời gian unix thì hàm sẽ trả về là invalid date // thêm dấu + để chuyển đổi thời gian unix về ngày tháng bình thường if(isInvalidDate(date)) { date = new Date(+req.params.date) } // TH date không hợp lệ if(isInvalidDate(date)) { res.json({error: "Invalid Date"}); return; } res.json({unix: date.getTime() , utc:new Date(date).toUTCString()}) }); // nếu trống ngày thì sẽ lấy time hiện tại app.get('/api', (req,res) => { res.json({unix: new Date().getTime(), utc: new Date().toUTCString()}) }) // Listen on port set in environment variable or default to 3000 var listener = app.listen(process.env.PORT || 3000, function () { console.log('Your app is listening on port ' + listener.address().port); }); ``` ## 2. Request Header Parser Microservice ### Yêu cầu ![image](https://hackmd.io/_uploads/SJh3jlvgC.png) ### Solution bài này, để lấy được thông tin request được gửi lên, chỉ cần sử dụng `req` ```javascript app.get('/api/whoami', function (req, res) { res.json({ ipaddress: req.ip, language: req.headers["accept-language"], software: req.headers["user-agent"] }) }); ``` ## 3. URL Shortener Microservice ### Yêu cầu ![image](https://hackmd.io/_uploads/BkISTxPlC.png) bài nì yêu cầu là rút gọn url, như hình trên nếu ta thêm giá trị của short_url vào sau url hiện tại ![image](https://hackmd.io/_uploads/Bk5jAlDlR.png) thì trang sẽ chuyển hướng đến 1 trang khác, ở đây là sẽ đến gg ### Solution sang bài này thì mình thấy là no idea, đầu cứ nghĩ là short_url kia là sao không biết, ncl không hiểu lắm rồi mình đã tìm xem solution :((())) để giải quyết vấn đề, ta sẽ lưu trữ **url gốc** cùng với 1 giá trị **short_url** ứng riêng cho mình url đó (kiểu như id riêng của url đó) vào 1 document của csdl mongodb. Như vậy, ta sẽ tạo 1 schema với 2 thuộc tính là `original_url`, `short_url` đến đây thì dễ rồi, khi muốn ghi thêm short_url trên thanh url để chuyển hướng đến trang url gốc, ta sử dụng **`req.params`** để lấy giá trị short_url, sau đó ta sẽ tìm kiếm dữ liệu lưu trữ tại các document xem có tồn tại short_url đó chưa, nếu có sẽ chuyển hướng đến trang url gốc bằng **`res.redirect`**, còn không có sẽ tạo thêm 1 document để lưu 1 short_url mới cùng url mới đó file **index.js** ```javascript require('dotenv').config(); const express = require('express'); const cors = require('cors'); const app = express(); const mongoose = require('mongoose'); let bodyParser = require('body-parser'); const dns = require('node:dns'); // ket noi toi mongo db mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log('Connected to MongoDB')) .catch(err => console.error('Failed to connect to MongoDB', err)); // tao 1 schema voi cac thuoc tinh tuong ung const URLSchema = new mongoose.Schema({ original_url: {type: String, required: true, unique: true}, short_url: {type: String, required: true, unique: true} }) // tao 1 instance const URLModel = mongoose.model('url', URLSchema); // Basic Configuration const port = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); app.use(express.urlencoded({extended : true})) app.use('/public', express.static(`${process.cwd()}/public`)); app.get('/', function(req, res) { res.sendFile(process.cwd() + '/views/index.html'); }); // Your first API endpoint app.get('/api/shorturl/:short_url', function(req, res) { let short_url = req.params.short_url; URLModel.findOne({short_url: short_url}).then( (foundURL) => { if(foundURL){ let original_url = foundURL.original_url; res.redirect(original_url); } else { res.json({message: "The short url does not exist!"}) } }) }) app.post('/api/shorturl', (req,res) => { let url = req.body.url; try{ urlObj = new URL(url); console.log(urlObj); // kiem tra url co hop le hay khong dns.lookup(urlObj.hostname, (err, address, family) => { if(!address){ res.json({error: 'Invalid url'}) } else { let original_url = urlObj.href; // kiem tra url da ton tai hay chua, co thi se chuyen huong toi url, // khong thi se tao moi 1 short_url tuong ung URLModel.findOne({original_url: original_url}).then( (foundURL) => { if(foundURL) { res.redirect(foundURL.original_url); } else { let short_url = 1; URLModel.find({}).sort({short_url: 'desc'}).limit(1).then( (lastestURL) => { if(lastestURL.length > 0) { short_url = parseInt(lastestURL[0].short_url) + 1; } resObj = { original_url: original_url, short_url: short_url } let newURL = new URLModel(resObj); newURL.save(); res.json(resObj); console.log(resObj) }) } }) } }) } catch{ res.json({error: 'Invalid url'}) } }) app.listen(port, function() { console.log(`Listening on port ${port}`); }); ``` ## 4. File Metadata Microservice ### Yêu cầu ![image](https://hackmd.io/_uploads/rJhdz0wgC.png) sau khi upload file thì sẽ phản hồi về các thông số của file như dưới ![image](https://hackmd.io/_uploads/ByXoG0veA.png) ### Solution bài nì đầu mình nghĩ chắc là vẫn dùng `req` để lấy được thông tin như thường thôi, nhưng không, hiện thực đã vả mừn. sau đó mình đã lên searchgg về cách upload file trong express, thì mình tìm được là cần có middleware để xử lý dữ liệu, ở đây mình sẽ sử dụng phần mềm trung gian là multer ta sẽ cấu hình multer để lưu trữ tệp được tải lên trong thư mục `uploads/` và giữ nguyên tên tệp gốc. Sau đó, chúng ta tạo một middleware Multer và sử dụng nó trong route `/api/fileanalyse` để xử lý file tải lên. sau khi đã cài middleware multer rồi thì ta sẽ xử lý dữ liệu upload lên như thường được rồi file **index.js** ```javascript var express = require('express'); var cors = require('cors'); require('dotenv').config() var bodyParser = require('body-parser') var app = express(); const multer = require('multer'); app.use(cors()); app.use('/public', express.static(process.cwd() + '/public')); // parse application/x-www-form-urlencoded app.use(bodyParser.urlencoded({ extended: false })) // parse application/json app.use(bodyParser.json()) // Cấu hình Multer const storage = multer.diskStorage({ destination: function (req, file, cb) { // Đường dẫn lưu trữ tệp cb(null, 'uploads/'); }, filename: function (req, file, cb) { // Đặt tên tệp cb(null, file.originalname); } }); // Tạo middleware Multer const upload = multer({ storage: storage }); // Sử dụng middleware Multer trong tuyến đường // 'upfile' ở đây là key name trong file tạo biểu mẫu html app.post('/api/fileanalyse', upload.single('upfile'), (req, res) => { let file = req.upfile; console.log(req.file); res.json({"name":file.originalname,"type":file.mimetype,"size":file.size}) }); app.get('/', function (req, res) { res.sendFile(process.cwd() + '/views/index.html'); }); const port = process.env.PORT || 3000; app.listen(port, function () { console.log('Your app is listening on port ' + port) }); ``` ## 5. File Metadata Microservice ### Yêu cầu ![image](https://hackmd.io/_uploads/SkZ4F-PeC.png) khi submit thì response trả về là tài liệu json hiển thị giá trị vừa submit tạo 1 GET request tới `/api/users/:_id/logs` sẽ hiển thị tất cả exercise của user ![image](https://hackmd.io/_uploads/rJSZyMvgC.png) và yêu cầu cuối ![image](https://hackmd.io/_uploads/SytVkzPxC.png) ### Solution ở bài này thì thực hiện 2 yêu cầu đầu khá đơn giản, ta sẽ kết nối project với mongodb để lưu trữ dữ liệu được submit lên ta sẽ tạo 2 collection (có thể hiểu là 2 table), 1 cái là cho user, cái nữa là cho exercise để route `/api/users/:_id/logs` có thể hiển thị tên user cùng thông tin exercise, ta sẽ dùng function `Model.find()` để tìm kiếm userId ở cả hai collection vừa tạo, sau đó sẽ in ra thông tin của user đó đến phần lấy tham số truy vấn `from`, `to`, `limit` từ request gửi lên, ta sẽ sử dụng `req.query` để lấy query gửi lên, sau đó sẽ dùng `.find()` để tìm kiếm date, `.limit()` để giới hạn số bản ghi muốn lấy ```javascript const express = require('express') const app = express() const cors = require('cors') require('dotenv').config() const bodyParser = require('body-parser') const mongoose = require('mongoose') // kết nối mongodb mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log('Connected to MongoDB')) .catch(err => console.error('Failed to connect to MongoDB', err)); // tao 1 schema voi cac thuoc tinh tuong ung const userSchema = new mongoose.Schema({ username: {type: String, required: true}, }) // tạo 1 instance lưu trữ thông tin user const userModel = mongoose.model('user', userSchema); // tao 1 schema exercise voi cac thuoc tinh tuong ung const exerciseSchema = new mongoose.Schema({ userId: {type: String, required: true}, description: {type: String, required: true}, duration: {type: String, required: true}, date: {type: Date, default: new Date()}, }) // tạo 1 instance lưu trữ thông tin của exercise const exerciseModel = mongoose.model('exercise', exerciseSchema); app.use(cors()) app.use(express.static('public')) app.use('/',bodyParser.urlencoded({ extended: false})) app.get('/', (req, res) => { res.sendFile(__dirname + '/views/index.html') }); app.get('/api/users', (req,res) => { userModel.find({}).then( (user) => { res.json(user); }) }) app.post('/api/users', (req,res) => { let username = req.body.username let newUser = new userModel({username: username}) newUser.save() res.json(newUser) }) app.post('/api/users/:_id/exercises',(req,res)=>{ let userId = req.params._id; let exerciseObj = { userId: userId, description: req.body.description, duration: req.body.duration, } if(req.body.date != '') { exerciseObj.date = req.body.date; } let newExercise = new exerciseModel(exerciseObj); userModel.findOne({_id: userId}).then((found) => { newExercise.save(); res.json({ _id: found._id, username: found.username, date: new Date(newExercise.date).toDateString(), duration: parseInt(newExercise.duration), description: newExercise.description }) }) }) app.get('/api/users/:_id/logs', (req,res) => { let userId = req.params._id let paramFrom = req.query.from let paramTo = req.query.to let paramLimit = req.query.limit // lấy tham số limit ở dạng số thay vì dạng chuỗi paramLimit = paramLimit ? parseInt(paramLimit) : paramLimit let resDate = { userId: userId } if(paramFrom || paramTo){ // tạo thêm thuộc tính date cho resDate resDate.date = {} if(paramFrom){ // sắp xếp date từ dưới lên, tính từ paramFrom resDate.date['$gte'] = paramFrom } if(paramTo){ // sắp xếp date từ trên xuống, tính từ paramTo resDate.date['$lte'] = paramTo } } userModel.findOne({_id: userId}).then( (found) => { exerciseModel.find(resDate).limit(paramLimit).then((foundExer) => { let logs = foundExer.map((x)=>{ return { description: x.description, duration: parseInt(x.duration), date: new Date(x.date).toDateString() } }) res.json({ _id: userId, username: found.username, log: logs, count: logs.length }) }) }) }) const listener = app.listen(process.env.PORT || 3000, () => { console.log('Your app is listening on port ' + listener.address().port) }) ```