Đăng nhập

BÀI 27: UPLOAD HÌNH ẢNH & XỬ LÝ FORM TRONG NUXT 3

Cùng tiếp tục với Bài 27 – Upload hình ảnh và xử lý form với Nuxt 3. Đây là kỹ năng quan trọng giúp bạn xây dựng tính năng như: đăng bài viết có ảnh, avatar người dùng, giao diện quản trị sản phẩm, hoặc kết nối cloud như Cloudinary, S3.


1. Mục tiêu bài học

  • Tạo form có input ảnh (file upload)
  • Preview ảnh trước khi gửi
  • Gửi form kiểu multipart/form-data
  • Lưu tạm ảnh (local) hoặc upload lên dịch vụ như Cloudinary

2. Form đơn giản có upload ảnh

<template>
  <form @submit.prevent="submitForm">
    <input type="text" v-model="title" placeholder="Tên ảnh" />
    <input type="file" accept="image/*" @change="onFileChange" />
    <img v-if="preview" :src="preview" alt="preview" style="max-width:200px;" />
    <button type="submit">Gửi</button>
  </form>
</template>

<script setup>
const title = ref('')
const file = ref(null)
const preview = ref(null)

function onFileChange(e) {
  const selected = e.target.files[0]
  if (!selected) return
  file.value = selected
  preview.value = URL.createObjectURL(selected)
}

async function submitForm() {
  const formData = new FormData()
  formData.append('title', title.value)
  formData.append('image', file.value)

  try {
    await $fetch('/api/upload', {
      method: 'POST',
      body: formData
    })
    alert('Upload thành công!')
  } catch (err) {
    console.error(err)
    alert('Lỗi upload!')
  }
}
</script>

3. API xử lý upload

Tạo file: server/api/upload.post.ts

import formidable from 'formidable'
import { writeFile } from 'fs/promises'
import path from 'path'

export default defineEventHandler(async (event) => {
  const form = formidable({ multiples: false })

  const [fields, files] = await form.parse(event.node.req)

  const file = files.image?.[0]
  if (!file) throw createError({ statusCode: 400, statusMessage: 'No file' })

  const tempPath = file.filepath
  const filename = file.originalFilename
  const uploadDir = path.join(process.cwd(), 'public', 'uploads')

  await writeFile(path.join(uploadDir, filename), await Bun.file(tempPath).arrayBuffer())

  return { success: true, path: `/uploads/${filename}` }
})

Lưu ý:

  • Tạo thư mục /public/uploads để chứa ảnh
  • Bun.file() nếu dùng Bun, hoặc fs.readFileSync() nếu bạn chạy Node

4. Dùng Cloudinary thay vì lưu local (tuỳ chọn)

Cài gói:

npm install cloudinary

Tạo file .env:

CLOUDINARY_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret

Trong API:

import { v2 as cloudinary } from 'cloudinary'
import { readFile } from 'fs/promises'

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET
})

const result = await cloudinary.uploader.upload(tempPath, {
  folder: 'nuxt-images'
})

→ Trả về result.secure_url để hiển thị ảnh.


5. Gợi ý nâng cao UX

  • Hiển thị loading khi đang upload
  • Tự reset form sau khi upload thành công
  • Tạo list ảnh đã upload
  • Kết hợp vee-validate để kiểm tra ảnh

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

  • Tạo form upload ảnh đơn giản
  • Preview ảnh trước khi gửi
  • Gửi FormData đến server
  • Tạo API lưu ảnh local hoặc upload Cloudinary
  • Xử lý form + trạng thái upload hiệu quả

Bài tập mở rộng

  1. Thêm upload nhiều ảnh cùng lúc
  2. Tạo giao diện quản lý ảnh (xem, xoá, sửa tên)
  3. Kết hợp upload + submit nội dung bài viết
  4. Tự tạo hook useImageUploader() để tái sử dụng

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.