Nội dung bài họcLesson content
Full-stack là gì?What is full-stack?
Full-stack là kết hợp frontend (giao diện người dùng — HTML/CSS/JS), backend (logic server — Node.js/Express) và database (lưu trữ — SQLite). Ứng dụng Todo full-stack: user thêm/sửa/xóa task trên web → frontend gọi API → backend xử lý → lưu database.Full-stack combines frontend (user interface — HTML/CSS/JS), backend (server logic — Node.js/Express), and database (storage — SQLite). A full-stack Todo app: user adds/edits/deletes tasks on web → frontend calls API → backend processes → saves to database.
Frontend gọi API với fetch()Frontend calls API with fetch()
fetch(url, options) là hàm JavaScript gọi API. GET: fetch("/tasks"). POST: fetch("/tasks", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(data) }). fetch trả Promise — dùng await hoặc .then() để lấy response.fetch(url, options) is the JavaScript function to call APIs. GET: fetch("/tasks"). POST: fetch("/tasks", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify(data) }). fetch returns a Promise — use await or .then() to get the response.
// GET: lấy danh sách task
async function loadTasks() {
const res = await fetch('/tasks');
const data = await res.json();
renderTasks(data.data);
}
// POST: tạo task mới
async function addTask(title) {
const res = await fetch('/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title })
});
const data = await res.json();
loadTasks(); // Tải lại danh sách
}
Cập nhật UI không reload trangUpdating UI without page reload
Khi thêm task, không cần reload trang. Dùng JavaScript: tạo element mới, thêm vào DOM. element.innerHTML hoặc document.createElement + appendChild. Đây là cách SPA (Single Page Application) hoạt động — mượt mà, nhanh.When adding a task, no need to reload the page. Use JavaScript: create a new element, add to DOM. element.innerHTML or document.createElement + appendChild. This is how SPAs (Single Page Applications) work — smooth and fast.
function renderTasks(tasks) {
const list = document.querySelector('#task-list');
list.innerHTML = ''; // Xóa cũ
tasks.forEach(task => {
const li = document.createElement('li');
li.textContent = task.title;
if (task.done) li.classList.add('done');
list.appendChild(li);
});
}
Backend lưu vào SQLiteBackend saves to SQLite
Backend nhận request, thực hiện SQL query lên SQLite, trả response. Dùng better-sqlite3 (đồng bộ) hoặc sqlite3 (callback). Mỗi endpoint tương ứng 1 SQL operation: GET → SELECT, POST → INSERT, PUT → UPDATE, DELETE → DELETE.The backend receives requests, executes SQL queries on SQLite, returns responses. Use better-sqlite3 (synchronous) or sqlite3 (callback). Each endpoint corresponds to a SQL operation: GET → SELECT, POST → INSERT, PUT → UPDATE, DELETE → DELETE.
const Database = require('better-sqlite3');
const db = new Database('todo.db');
db.exec(`CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER DEFAULT 0
)`);
app.get('/tasks', (req, res) => {
const tasks = db.prepare('SELECT * FROM tasks').all();
res.json({ success: true, data: tasks });
});
app.post('/tasks', (req, res) => {
const info = db.prepare('INSERT INTO tasks (title) VALUES (?)').run(req.body.title);
res.status(201).json({ success: true, data: { id: info.lastInsertRowid, title: req.body.title } });
});