This guide will help you create a complete file management system with Vue 3 frontend and Spring Boot 3 backend.
Project Structure
Frontend (Vue 3) Directory Structure

Backend (Spring Boot 3) Directory Structure

Frontend Implementation
1. Set up Vue 3 project
npm create vue@latest file-manager-frontend
cd file-manager-frontend
npm install axios pinia vue-router
npm run dev
2. Main components
FileUpload.vue
<template>
<div class="file-upload">
<input type="file" @change="handleFileChange" multiple />
<button @click="uploadFiles" :disabled="!files.length">Upload</button>
<div v-if="progress > 0" class="progress-bar">
<div :style="{ width: progress + '%' }"></div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useFileStore } from '@/stores/fileStore';
const fileStore = useFileStore();
const files = ref([]);
const progress = ref(0);
const handleFileChange = (e) => {
files.value = Array.from(e.target.files);
};
const uploadFiles = async () => {
if (!files.value.length) return;
const formData = new FormData();
files.value.forEach(file => {
formData.append('files', file);
});
try {
await fileStore.uploadFiles(formData, (event) => {
progress.value = Math.round((event.loaded * 100) / event.total);
});
files.value = [];
progress.value = 0;
} catch (error) {
console.error('Upload failed:', error);
}
};
</script>
FileList.vue
<template>
<div class="file-list">
<table>
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
<th>Uploaded</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="file in files" :key="file.id">
<td>{{ file.name }}</td>
<td>{{ formatSize(file.size) }}</td>
<td>{{ file.type }}</td>
<td>{{ formatDate(file.uploadedAt) }}</td>
<td>
<button @click="downloadFile(file.id, file.name)">Download</button>
<button @click="deleteFile(file.id)">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useFileStore } from '@/stores/fileStore';
const fileStore = useFileStore();
const files = computed(() => fileStore.files);
const formatSize = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleString();
};
const downloadFile = async (id, name) => {
await fileStore.downloadFile(id, name);
};
const deleteFile = async (id) => {
if (confirm('Are you sure you want to delete this file?')) {
await fileStore.deleteFile(id);
}
};
</script>
fileStore.js (Pinia store)
import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';
export const useFileStore = defineStore('file', () => {
const files = ref([]);
const error = ref(null);
const API_URL = 'http://localhost:8080/api/files';
const fetchFiles = async () => {
try {
const response = await axios.get(API_URL);
files.value = response.data;
} catch (err) {
error.value = err.message;
}
};
const uploadFiles = async (formData, onUploadProgress) => {
try {
await axios.post(API_URL, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress
});
await fetchFiles();
} catch (err) {
error.value = err.message;
throw err;
}
};
const downloadFile = async (id, name) => {
try {
const response = await axios.get(`${API_URL}/${id}/download`, {
responseType: 'blob'
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', name);
document.body.appendChild(link);
link.click();
link.remove();
} catch (err) {
error.value = err.message;
}
};
const deleteFile = async (id) => {
try {
await axios.delete(`${API_URL}/${id}`);
await fetchFiles();
} catch (err) {
error.value = err.message;
}
};
return {
files,
error,
fetchFiles,
uploadFiles,
downloadFile,
deleteFile
};
});
Backend Implementation
1. Set up Spring Boot project
Use Spring Initializr (https://start.spring.io/) with these dependencies:
- Spring Web
- Spring Data JPA
- H2 Database (or your preferred database)
- Lombok
2. Main components
FileInfo.java (Entity)
package com.example.filemanager.model;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import java.util.Date;
@Entity
@Data
public class FileInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String type;
private long size;
private String storagePath;
@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
private Date uploadedAt;
}
FileInfoDto.java (DTO)
package com.example.filemanager.dto;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class FileInfoDto {
private Long id;
private String name;
private String type;
private long size;
private LocalDateTime uploadedAt;
}
ResponseMessage.java
package com.example.filemanager.dto;
import lombok.Data;
@Data
public class ResponseMessage {
private String message;
public ResponseMessage(String message) {
this.message = message;
}
}
FileStorageService.java (Interface)
package com.example.filemanager.service;
import com.example.filemanager.model.FileInfo;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
public interface FileStorageService {
void init();
FileInfo save(MultipartFile file);
List<FileInfo> saveAll(MultipartFile[] files);
Resource load(Long fileId);
void delete(Long fileId);
void deleteAll();
Stream<FileInfo> loadAll();
}
FileStorageServiceImpl.java
package com.example.filemanager.service;
import com.example.filemanager.exception.FileStorageException;
import com.example.filemanager.model.FileInfo;
import com.example.filemanager.repository.FileInfoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
public class FileStorageServiceImpl implements FileStorageService {
@Value("${file.upload-dir}")
private String uploadDir;
@Autowired
private FileInfoRepository fileInfoRepository;
@Override
public void init() {
try {
Files.createDirectories(Paths.get(uploadDir));
} catch (IOException e) {
throw new FileStorageException("Could not initialize storage directory", e);
}
}
@Override
public FileInfo save(MultipartFile file) {
String filename = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));
try {
if (filename.contains("..")) {
throw new FileStorageException("Filename contains invalid path sequence: " + filename);
}
String uniqueFilename = UUID.randomUUID() + "_" + filename;
Path targetLocation = Paths.get(uploadDir).resolve(uniqueFilename);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
FileInfo fileInfo = new FileInfo();
fileInfo.setName(filename);
fileInfo.setType(file.getContentType());
fileInfo.setSize(file.getSize());
fileInfo.setStoragePath(targetLocation.toString());
return fileInfoRepository.save(fileInfo);
} catch (IOException e) {
throw new FileStorageException("Failed to store file " + filename, e);
}
}
@Override
public List<FileInfo> saveAll(MultipartFile[] files) {
return Arrays.stream(files)
.map(this::save)
.collect(Collectors.toList());
}
@Override
public Resource load(Long fileId) {
FileInfo fileInfo = fileInfoRepository.findById(fileId)
.orElseThrow(() -> new FileStorageException("File not found with id: " + fileId));
try {
Path filePath = Paths.get(fileInfo.getStoragePath());
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists() || resource.isReadable()) {
return resource;
} else {
throw new FileStorageException("Could not read file: " + fileInfo.getName());
}
} catch (MalformedURLException e) {
throw new FileStorageException("Could not read file: " + fileInfo.getName(), e);
}
}
@Override
public void delete(Long fileId) {
FileInfo fileInfo = fileInfoRepository.findById(fileId)
.orElseThrow(() -> new FileStorageException("File not found with id: " + fileId));
try {
Path filePath = Paths.get(fileInfo.getStoragePath());
Files.deleteIfExists(filePath);
fileInfoRepository.delete(fileInfo);
} catch (IOException e) {
throw new FileStorageException("Could not delete file: " + fileInfo.getName(), e);
}
}
@Override
public void deleteAll() {
fileInfoRepository.deleteAll();
try {
Files.walk(Paths.get(uploadDir))
.filter(Files::isRegularFile)
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
System.err.println("Failed to delete " + path + ": " + e.getMessage());
}
});
} catch (IOException e) {
throw new FileStorageException("Failed to delete files", e);
}
}
@Override
public Stream<FileInfo> loadAll() {
return fileInfoRepository.findAll().stream();
}
}
FileController.java
package com.example.filemanager.controller;
import com.example.filemanager.dto.FileInfoDto;
import com.example.filemanager.dto.ResponseMessage;
import com.example.filemanager.model.FileInfo;
import com.example.filemanager.service.FileStorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/files")
@CrossOrigin(origins = "http://localhost:5173",
allowedHeaders = "*",
methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE},
allowCredentials = "true")
public class FileController {
@Autowired
private FileStorageService storageService;
@GetMapping
public ResponseEntity<List<FileInfoDto>> listFiles() {
List<FileInfoDto> files = storageService.loadAll()
.map(this::mapToFileInfoDto)
.collect(Collectors.toList());
return ResponseEntity.ok(files);
}
@PostMapping
public ResponseEntity<ResponseMessage> uploadFiles(@RequestParam("files") MultipartFile[] files) {
try {
storageService.saveAll(files);
return ResponseEntity.ok(new ResponseMessage("Files uploaded successfully"));
} catch (Exception e) {
return ResponseEntity.badRequest()
.body(new ResponseMessage("Failed to upload files: " + e.getMessage()));
}
}
@GetMapping("/{id}/download")
public ResponseEntity<Resource> downloadFile(@PathVariable Long id) {
Resource file = storageService.load(id);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"")
.body(file);
}
@DeleteMapping("/{id}")
public ResponseEntity<ResponseMessage> deleteFile(@PathVariable Long id) {
try {
storageService.delete(id);
return ResponseEntity.ok(new ResponseMessage("File deleted successfully"));
} catch (Exception e) {
return ResponseEntity.badRequest()
.body(new ResponseMessage("Failed to delete file: " + e.getMessage()));
}
}
private FileInfoDto mapToFileInfoDto(FileInfo fileInfo) {
String downloadUrl = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/files/")
.path(fileInfo.getId().toString())
.path("/download")
.toUriString();
FileInfoDto dto = new FileInfoDto();
dto.setId(fileInfo.getId());
dto.setName(fileInfo.getName());
dto.setType(fileInfo.getType());
dto.setSize(fileInfo.getSize());
dto.setUploadedAt(fileInfo.getUploadedAt().toInstant());
return dto;
}
}
application.properties
# Server port
server.port=8080
# File storage
file.upload-dir=uploads
# H2 Database
spring.datasource.url=jdbc:h2:mem:filemanager
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# H2 Console
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JPA
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
Running the Application
Backend
- Start the Spring Boot application
- Access H2 console at http://localhost:8080/h2-console
- JDBC URL: jdbc:h2:mem:filemanager
- Username: sa
- Password: (empty)
Frontend
- Run
npm run dev
- Access the application at http://localhost:5173
Features Implemented
- File upload (single/multiple)
- File listing with metadata
- File download
- File deletion
- Progress tracking for uploads
- Error handling
- Responsive UI
Security Considerations (To Implement)
- Add authentication (JWT)
- Implement file access control
- Add file size limits
- Implement virus scanning for uploads
- Add rate limiting
Deployment Considerations
- Use proper database (PostgreSQL, MySQL) in production
- Configure proper file storage (S3, Azure Blob Storage)
- Set up proper CORS configuration
- Implement HTTPS
This implementation provides a complete foundation for a file management system that you can extend with additional features as needed.