Selam, ben frontend (nuxt 3) ve backend (nodejs) de proje yapıyorum kodlarım şöyle.
Backend
utils/fileUpload.js
import multer from "multer";
import path from "path";
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads');
},
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname));
// cb(null, `${new Date().toISOString().replace(/:/g, '-')}${file.originalname}`);
}
});
const upload = multer({
storage,
fileFilter: (req, file, cb) => {
if(file.mimetype === 'image/png' || file.mimetype === 'image/jpg' || file.mimetype === 'image/jpeg' || file.mimetype === 'image/gif') {
cb(null, true);
}else {
cb(new Error('MimeType not supported'), false);
}
},
// limits: {
// fileSize: 1024 * 1024 * 5
// },
});
export default upload;
bookRoute.js
router.post('/upload', authMiddleware.authenticateUser, upload.single('image'), bookController.uploadFile);
bookController.js
const uploadFile = async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
const filePath = req.file.path;
res.status(200).json({ message: 'File uploaded successfully', filePath });
} catch (error) {
console.error('Error uploading file', error);
res.status(500).json({ error: 'Internal Server ERROR' });
}
};
const store = async (req, res) => {
try {
upload.single('image')(req, res, async (err) => {
if (err) {
return res.status(400).json({ error: err.message });
}
const { name, author, description, page } = req.body;
const uploader = req.user._id;
const image = req.file ? req.file.path : null;
console.log('Uploaded file:', req.file);
console.log('Image path:', image || 'No file');
if (!image) { // Check if image was uploaded
return res.status(400).json({ error: 'Image upload failed.' });
}
const existingBook = await Book.findOne({ name, author });
if (existingBook) {
return res.status(400).json({ error: 'A book with same name and author already exist!' });
};
const newBook = await Book.create({
name,
author,
description,
page,
uploader,
image
});
return res.status(201).json({
message: 'Book created successfully',
books: newBook
});
});
} catch (error) {
// Handle validation errors
if (error.name === 'ValidationError') {
if (checkValidationErrors(error, res)) return;
} else {
console.error("Error at creating book", error);
return res
.status(500)
.json({ error: 'Internal Server ERROR' });
}
}
};
Frontend (Nuxt 3)
DashboardBooks.vue
<script setup lang="ts">
import type { Modal } from "bootstrap";
import { Dropzone } from "dropzone";
import "dropzone/dist/dropzone.css";
// import VueDropzone from 'vue3-dropzone';
import { useToast } from "vue-toastification";
import { useBookStore } from "~/store/bookStore";
import { useAuthStore } from "~/store/authStore";
import PaginationWidget from "../widgets/PaginationWidget.vue";
import type { Book } from "~/types";
// Use Nuxt App and Book Store
const { $bootstrap } = useNuxtApp();
const toast = useToast();
const bookStore = useBookStore();
const authStore = useAuthStore();
// Reactive state for the new book
let newBook = reactive<Book>({
name: "",
author: "",
description: "",
page: null,
editedBookId: null,
});
const modalTitle = ref<string>("Add Book");
const currentPage = ref<number>(1);
const itemsPerPage = ref<number>(2);
const dropzoneElement = ref<HTMLDivElement | null>(null);
let modal: Modal | undefined;
let dropzone: Dropzone | null = null;
// Methods
const saveBook = () => {
modalTitle.value === "Add Book" ? addBook() : editBook();
};
const updatePage = (page: number) => {
currentPage.value = page;
};
const openAddModal = () => {
modalTitle.value = "Add Book";
Object.assign(newBook, {
name: "",
author: "",
description: "",
page: null,
editedBook: null,
});
modal?.show();
};
const openEditModal = (existingBook: Book) => {
modalTitle.value = "Edit Book";
Object.assign(newBook, {
...existingBook,
editedBookId: existingBook._id,
});
modal?.show();
};
const addBook = async () => {
try {
await bookStore.addBook(newBook);
currentPage.value = 1;
modal?.hide();
Object.assign(newBook, {
name: "",
author: "",
description: "",
page: null,
editedBookId: null,
});
await bookStore.fetchBooksByUploader();
showToast("New book added successfully", {
type: "success",
position: "top-right",
timeout: 1000,
});
} catch (error) {
console.log(error);
}
};
const editBook = async () => {
try {
await bookStore.editTheBook(newBook.editedBookId, newBook);
await bookStore.fetchBooksByUploader();
modal?.hide();
showToast("The book edited successfully", {
type: "success",
timeout: 3000,
});
} catch (error) {
console.error(error);
}
};
const showToast = (message: string, options: object) => {
toast(message, {
position: "top-right",
closeButton: "button",
icon: true,
rtl: false,
...options,
});
};
const deleteBook = async (id: string, name: string) => {
try {
await bookStore.deleteTheBook(id);
await bookStore.fetchBooksByUploader();
showToast(`${name} deleted successfully`, {
type: "warning",
timeout: 3000,
});
} catch (error) {
console.error(error);
}
};
// Computed property for user books
const userBooks = computed(() => {
return bookStore.userUploadedBooks.slice().sort((a: Book, b: Book) => {
const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
return dateB - dateA;
});
});
const totalPages = computed(() => {
return Math.ceil(userBooks.value.length / itemsPerPage.value);
});
const paginatedBooks = computed(() => {
const startIndex = (currentPage.value - 1) * itemsPerPage.value;
const endIndex = startIndex + itemsPerPage.value;
return userBooks.value.slice(startIndex, endIndex);
});
// Lifecycle hook
onMounted(() => {
const modalElement = document.getElementById("modal-main");
if (modalElement) {
modal = new $bootstrap.Modal(modalElement);
}
bookStore.fetchBooksByUploader();
if (dropzoneElement.value) {
dropzone = new Dropzone(dropzoneElement.value, {
url: "http://localhost:5000/api/v1/books/upload",
thumbnailWidth: 150,
maxFilesize: 2, // Max file size in MB
dictDefaultMessage: "Drag files here or click to upload",
paramName: "image",
acceptedFiles: "image/*",
init: function () {
this.on("sending", (file, xhr, formData) => {
xhr.setRequestHeader("Authorization", `Bearer ${authStore.token}`);
});
this.on("success", (file, response) => {
// Handle success
// if (response.filePath) {
// newBook.image = response.filePath;
console.log("File uploaded successfully", response);
// } else {
// console.error("No file path returned.");
// }
});
this.on("error", (file, message) => {
// Handle error
console.error("Upload failed:", message);
});
},
});
}
});
onUnmounted(() => {
if (dropzone) {
dropzone.destroy(); // Clean up Dropzone instance when component unmounts
}
});
</script>
<template>
<div>
<!-- Button -->
<div class="row mb-3">
<div class="col text-end">
<button type="button" class="btn btn-primary" @click="openAddModal()">
Add Book
</button>
</div>
</div>
<!-- Table -->
<div class="row">
<div class="col">
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Description</th>
<th>Page</th>
<th class="text-center">Edit</th>
<th class="text-center">Delete</th>
</tr>
</thead>
<TransitionGroup name="list" tag="tbody">
<tr v-for="book in paginatedBooks" :key="book._id">
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td style="max-width: 250px">
{{ book.description }}
</td>
<td>{{ book.page }}</td>
<td class="text-center">
<font-awesome
:icon="['far', 'pen-to-square']"
class="text-warning"
style="cursor: pointer"
@click="openEditModal(book)"
/>
</td>
<td class="text-center">
<font-awesome
:icon="['fas', 'trash']"
class="text-danger"
style="cursor: pointer"
@click="deleteBook(book._id, book.name)"
/>
</td>
</tr>
</TransitionGroup>
</table>
</div>
</div>
<div class="row">
<PaginationWidget
:currentPage="currentPage"
:totalPages="totalPages"
@page-changed="updatePage"
/>
</div>
<!-- Modal -->
<div class="modal fade" id="modal-main" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addModalLabel">{{ modalTitle }}</h5>
<button
type="button"
@click="modal.hide()"
class="btn-close"
aria-label="Close"
></button>
</div>
<div class="modal-body mx-5">
<div class="col mb-3">
<label for="title" class="form-label"
>Name
<span class="text-danger">*</span>
</label>
<input
type="text"
class="form-control"
id="name"
name="name"
v-model="newBook.name"
required
/>
</div>
<div class="col mb-3">
<label for="author" class="form-label"
>Author
<span class="text-danger">*</span>
</label>
<input
type="text"
class="form-control"
id="author"
name="author"
v-model="newBook.author"
required
/>
</div>
<div class="col mb-3">
<label for="description" class="form-label"
>Description
<span class="text-danger">*</span>
</label>
<textarea
name="description"
id="description"
class="form-control"
cols="30"
rows="4"
v-model="newBook.description"
></textarea>
</div>
<div class="col mb-3">
<label for="author" class="form-label"
>Number of Pages
<span class="text-danger">*</span>
</label>
<input
type="number"
class="form-control"
id="numOfPages"
name="numOfPages"
v-model="newBook.page"
required
/>
</div>
<div ref="dropzoneElement" class="my-dropzone"></div>
<div class="text-end mb-4">
<button
@click="modal.hide()"
type="button"
class="btn btn-outline-secondary"
>
Close
</button>
<button @click="saveBook()" type="button" class="btn btn-primary">
Save
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
bu kodlardakı önemli kısım
onMounted(() => {
const modalElement = document.getElementById("modal-main");
if (modalElement) {
modal = new $bootstrap.Modal(modalElement);
}
bookStore.fetchBooksByUploader();
if (dropzoneElement.value) {
dropzone = new Dropzone(dropzoneElement.value, {
url: "http://localhost:5000/api/v1/books/upload",
thumbnailWidth: 150,
maxFilesize: 2, // Max file size in MB
dictDefaultMessage: "Drag files here or click to upload",
paramName: "image",
acceptedFiles: "image/*",
init: function () {
this.on("sending", (file, xhr, formData) => {
xhr.setRequestHeader("Authorization", `Bearer ${authStore.token}`);
});
this.on("success", (file, response) => {
// Handle success
// if (response.filePath) {
// newBook.image = response.filePath;
console.log("File uploaded successfully", response);
// } else {
// console.error("No file path returned.");
// }
});
this.on("error", (file, message) => {
// Handle error
console.error("Upload failed:", message);
});
},
});
}
});
Foto yükler yüklemez consolda
File uploaded successfully {message: 'File uploaded successfully', filePath: 'uploads/1725613201618.jpg'}
geliyor oda
console.log("File uploaded successfully", response);
buradan gelyor
böylece resimi backendde olan uploads
klasörüne yüklemiş oluyor fakat. Submit yaptığımda (yani save). Console da hata çıkıyor
POST http://127.0.0.1:5000/api/v1/books 400 (Bad Request)
Request URL: http://127.0.0.1:5000/api/v1/books
Request Method: POST
Status Code: 400 Bad Request
Remote Address: 127.0.0.1:5000
Referrer Policy: strict-origin-when-cross-origin
{
"name": "Hilary Slater",
"author": "567",
"description": "Magna sunt do volupt",
"page": 36,
"editedBookId": null,
"editedBook": null
}
{
"error": "Image upload failed."
}
bu frontend tarafındakı hatalar.
backend tarafında ise
console.log('Uploaded file:', req.file);
console.log('Image path:', image || 'No file');
bunlardan
Server listening on 5000
Uploaded file: undefined
Image path: No file
böyle hata geliyor. Ve ben database image yolunu yazamıyorum.
Gariptir
const uploadFile = async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
console.log(req.file.path, 'REQ FILE PATH');
const filePath = req.file.path;
res.status(200).json({ message: 'File uploaded successfully', filePath });
} catch (error) {
console.error('Error uploading file', error);
res.status(500).json({ error: 'Internal Server ERROR' });
}
};
burada req.file
geliyor
uploads/1725618211152.jpg REQ FILE PATH
ama store
da gelmiyor. Route
router
.route('/')
.get(bookController.index)
.post(authMiddleware.authenticateUser, upload.single('image'), bookController.store);
böyle yaptım belki olur ama yine olmadı.
DÜZELTME
formData.append("name", newBook.name);
formData.append("author", newBook.author);
formData.append("description", newBook.description);
formData.append("page", newBook.page);
böyle düzelttim fakat bu seferde
A book with same name and author already exist!
hatası alıyorum