📘 BÀI 20: KẾT NỐI BACKEND – LƯU TODO QUA API THẬT (NodeJS + Express + MongoDB)

jk5587725

By jk5587725

Đăng ngày Tháng 7 7, 2025

Kết nối Backend để lưu Todo thật giúp ứng dụng Vue hoạt động như một sản phẩm thực tế, với hệ thống lưu todo vào cơ sở dữ liệu, có thể dùng được đa thiết bị và hỗ trợ đăng nhập sau này.

Mục tiêu:

  • Xây dựng REST API bằng NodeJS
  • Lưu dữ liệu vào MongoDB
  • Kết nối từ Vue (frontend) → gọi API thay vì giả lập
  • Hướng tới việc có multi-user + authentication

Phần 1: Tạo Backend với Express

1.1 Khởi tạo project Node

mkdir todo-api
cd todo-api
npm init -y
npm install express mongoose cors

1.2 File index.js

const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')

const app = express()
const port = 5000

app.use(cors())
app.use(express.json())

mongoose.connect('mongodb://localhost:27017/todoapp')
  .then(() => console.log('MongoDB Connected'))

// Schema
const Todo = mongoose.model('Todo', {
  title: String,
  completed: Boolean,
  createdAt: {
    type: Date,
    default: Date.now
  }
})

// Routes
app.get('/todos', async (req, res) => {
  const todos = await Todo.find().sort({ createdAt: -1 })
  res.json(todos)
})

app.post('/todos', async (req, res) => {
  const todo = new Todo(req.body)
  await todo.save()
  res.json(todo)
})

app.put('/todos/:id', async (req, res) => {
  const todo = await Todo.findByIdAndUpdate(req.params.id, req.body, { new: true })
  res.json(todo)
})

app.delete('/todos/:id', async (req, res) => {
  await Todo.findByIdAndDelete(req.params.id)
  res.json({ message: 'Deleted' })
})

app.listen(port, () => {
  console.log(`🚀 API running at http://localhost:${port}`)
})

Phần 2: Kết nối từ Vue (Pinia Store)

Cập nhật file: src/store/todoStore.js

import { defineStore } from 'pinia'

const API_URL = 'http://localhost:5000/todos'

export const useTodoStore = defineStore('todo', {
  state: () => ({
    todos: [],
    loading: false,
    error: null
  }),
  actions: {
    async fetchTodos() {
      this.loading = true
      try {
        const res = await fetch(API_URL)
        this.todos = await res.json()
      } catch (e) {
        this.error = 'Lỗi kết nối backend'
      } finally {
        this.loading = false
      }
    },
    async addTodo(title) {
      const res = await fetch(API_URL, {
        method: 'POST',
        body: JSON.stringify({ title, completed: false }),
        headers: { 'Content-Type': 'application/json' }
      })
      const data = await res.json()
      this.todos.unshift(data)
    },
    async deleteTodo(id) {
      await fetch(`${API_URL}/${id}`, { method: 'DELETE' })
      this.todos = this.todos.filter(t => t._id !== id)
    },
    async toggleTodo(id) {
      const todo = this.todos.find(t => t._id === id)
      const res = await fetch(`${API_URL}/${id}`, {
        method: 'PUT',
        body: JSON.stringify({ ...todo, completed: !todo.completed }),
        headers: { 'Content-Type': 'application/json' }
      })
      const updated = await res.json()
      todo.completed = updated.completed
    }
  }
})

Lưu ý MongoDB:

  • Bạn cần MongoDB local (mongodb://localhost:27017) hoặc dùng MongoDB Atlas (miễn phí)
  • Dùng môi trường .env để ẩn thông tin nhạy cảm nếu deploy

Bạn đã học được:

Kỹ năng BackendMục đích
Express RouterTạo API RESTful
MongooseGiao tiếp với MongoDB
CORS + JSON BodyKết nối frontend Vue
Kết nối API thậtDữ liệu lưu được, dùng đa thiết bị

Bài tập mở rộng:

  1. Thêm field userId để phân biệt người dùng
  2. Triển khai token-based auth (JWT)
  3. Triển khai project này lên:
    • Backend: Render / Railway / Vercel
    • Frontend: Netlify / Firebase Hosting

📂 Chuyên mục:

Thảo luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Đăng ký nhận tin mới

Nhận bài học, tài nguyên và cơ hội việc làm qua email hàng tuần.

Chúng tôi cam kết không spam. Bạn có thể hủy bất cứ lúc nào.