Xây dựng dự án CRUD nhỏ (Blog/Todo App)
Trong bài này, học viên sẽ thực hành xây dựng một ứng dụng CRUD nhỏ sử dụng Next.js: có thể là Blog hoặc Todo App. Mục tiêu là giúp học viên áp dụng các kỹ năng đã học như tạo route động, gọi API nội bộ, dùng fetch để gửi request từ client, styling với Tailwind, và quản lý danh sách dữ liệu đơn giản.

Xây dựng dự án CRUD
1. Cấu trúc dự án
/pages
/api
/todos.js ← API list/create
/todos/[id].js ← API update/delete/get
/todos
/index.js ← Danh sách
/[id].js ← Chi tiết todo
/create.js ← Tạo mới
/[id]/edit.js ← Sửa
/components
/TodoForm.js
/TodoItem.js
Bạn có thể thay "todos" bằng "posts" nếu làm dạng Blog.
2. API Routes
pages/api/todos.js
let todos = [
{ id: 1, title: 'Học Next.js', done: false },
{ id: 2, title: 'Viết blog', done: true }
]
export default function handler(req, res) {
if (req.method === 'GET') {
res.status(200).json(todos)
} else if (req.method === 'POST') {
const newTodo = {
id: Date.now(),
title: req.body.title,
done: false,
}
todos.push(newTodo)
res.status(201).json(newTodo)
}
}
pages/api/todos/[id].js
export default function handler(req, res) {
const { id } = req.query
const index = todos.findIndex(t => t.id == id)
if (index === -1) return res.status(404).json({ message: 'Không tìm thấy' })
if (req.method === 'GET') {
return res.status(200).json(todos[index])
}
if (req.method === 'PUT') {
todos[index] = { ...todos[index], ...req.body }
return res.status(200).json(todos[index])
}
if (req.method === 'DELETE') {
const deleted = todos.splice(index, 1)
return res.status(200).json(deleted[0])
}
res.status(405).end()
}
3. Trang danh sách (/todos/index.js)
import Link from 'next/link'
import { useEffect, useState } from 'react'
export default function TodoList() {
const [todos, setTodos] = useState([])
useEffect(() => {
fetch('/api/todos')
.then(res => res.json())
.then(setTodos)
}, [])
return (
<div className="p-4">
<h1 className="text-xl font-bold">Danh sách Todo</h1>
<Link href="/todos/create" className="text-blue-600 underline">+ Tạo mới</Link>
<ul className="mt-4">
{todos.map(todo => (
<li key={todo.id} className="border-b py-2">
<Link href={`/todos/${todo.id}`} className="hover:underline">
{todo.title}
</Link>
</li>
))}
</ul>
</div>
)
}
4. Tạo Todo mới (/todos/create.js)
import { useState } from 'react'
import { useRouter } from 'next/router'
export default function CreateTodo() {
const [title, setTitle] = useState('')
const router = useRouter()
const handleSubmit = async (e) => {
e.preventDefault()
await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title })
})
router.push('/todos')
}
return (
<div className="p-4">
<h1 className="text-lg font-bold">Tạo Todo mới</h1>
<form onSubmit={handleSubmit} className="mt-4 space-y-2">
<input
value={title}
onChange={e => setTitle(e.target.value)}
placeholder="Nhập nội dung..."
className="border p-2 w-full"
/>
<button className="bg-blue-600 text-white px-4 py-2 rounded">Lưu</button>
</form>
</div>
)
}
5. Chi tiết + Xoá (/todos/[id].js)
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
export default function TodoDetail() {
const { id } = useRouter().query
const [todo, setTodo] = useState(null)
const router = useRouter()
useEffect(() => {
if (!id) return
fetch(`/api/todos/${id}`)
.then(res => res.json())
.then(setTodo)
}, [id])
const handleDelete = async () => {
await fetch(`/api/todos/${id}`, { method: 'DELETE' })
router.push('/todos')
}
if (!todo) return <p>Loading...</p>
return (
<div className="p-4">
<h1 className="text-xl font-bold">{todo.title}</h1>
<p className="mt-2">Trạng thái: {todo.done ? 'Hoàn thành' : 'Chưa xong'}</p>
<button onClick={handleDelete} className="mt-4 bg-red-600 text-white px-4 py-2 rounded">
Xoá
</button>
</div>
)
}
6. Styling với Tailwind
Toàn bộ giao diện sử dụng các tiện ích Tailwind để giữ giao diện gọn gàng, dễ đọc và responsive. Bạn có thể mở rộng thêm theme hoặc custom class nếu muốn.
Kết luận
Sau bài học này, học viên đã thực hành:
- Thiết kế API routes phục vụ CRUD (GET, POST, PUT, DELETE)
- Tạo các trang giao diện cho danh sách, chi tiết, thêm, sửa, xoá
- Gọi API bằng
fetch
và cập nhật UI theo trạng thái - Styling hiệu quả bằng Tailwind CSS
- Sử dụng dynamic routing trong Next.js
Dự án CRUD nhỏ này là nền tảng để xây dựng các dự án thực tế lớn hơn như blog cá nhân, trình quản lý công việc, hoặc dashboard người dùng có đăng nhập.

Với hơn 10 năm kinh nghiệm lập trình web và từng làm việc với nhiều framework, ngôn ngữ như PHP, JavaScript, React, jQuery, CSS, HTML, CakePHP, Laravel..., tôi hy vọng những kiến thức được chia sẻ tại đây sẽ hữu ích và thiết thực cho các bạn.
Xem thêm

Chào, tôi là Vũ. Đây là blog hướng dẫn lập trình của tôi.
Liên hệ công việc qua email dưới đây.
lhvuctu@gmail.com