# 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

### 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

### 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

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

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

sau khi upload file thì sẽ phản hồi về các thông số của file như dưới

### 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

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

và yêu cầu cuối

### 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)
})
```