CODE WITH SIBIN

Solving Real Problems with Real Code


Spring Boot + Angular: File Upload & Download Example

This in-depth guide will walk you through creating a complete file upload and download system using Spring Boot for the backend and Angular for the frontend. We'll cover everything from project setup to advanced features.

Table of Contents

  1. Project Setup
  2. Spring Boot Backend
  3. Angular Frontend
  4. Advanced Features
  5. Deployment Considerations

Project Setup

Backend (Spring Boot)

  1. Create a new Spring Boot project using:

Select:

  • Maven or Gradle (we'll use Maven in this guide)
  • Java 11 or higher
  • Spring Boot 2.7.x or 3.x
  • Dependencies: Spring Web, Spring Data JPA (optional for database storage)

Frontend (Angular)

  1. Install Angular CLI if you haven't:bashCopyDownloadnpm install -g @angular/cli
  2. Create a new Angular project:bashCopyDownloadng new file-upload-download-ui cd file-upload-download-ui

Spring Boot Backend

Dependencies

Add these to your pom.xml:

<dependencies>
    <!-- Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- File Operations -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
    
    <!-- Optional: Database storage -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Validation -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
</dependencies>

Configuration

Create a configuration class to handle file storage settings:

@Configuration
public class FileStorageConfig {
    
    @Value("${file.upload-dir}")
    private String uploadDir;
    
    @Bean
    public Path fileStorageLocation() {
        Path fileStorageLocation = Paths.get(uploadDir).toAbsolutePath().normalize();
        
        try {
            Files.createDirectories(fileStorageLocation);
            return fileStorageLocation;
        } catch (Exception ex) {
            throw new RuntimeException("Could not create upload directory", ex);
        }
    }
}

Add to application.properties:

file.upload-dir=uploads
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

File Storage Service

Create a service to handle file operations:

@Service
public class FileStorageService {
    
    private final Path fileStorageLocation;
    
    @Autowired
    public FileStorageService(FileStorageConfig fileStorageConfig) {
        this.fileStorageLocation = fileStorageConfig.fileStorageLocation();
    }
    
    public String storeFile(MultipartFile file) {
        // Normalize file name
        String fileName = StringUtils.cleanPath(file.getOriginalFilename());
        
        try {
            // Check for invalid characters
            if(fileName.contains("..")) {
                throw new FileStorageException("Invalid file name: " + fileName);
            }
            
            // Copy file to target location
            Path targetLocation = this.fileStorageLocation.resolve(fileName);
            Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
            
            return fileName;
        } catch (IOException ex) {
            throw new FileStorageException("Could not store file " + fileName, ex);
        }
    }
    
    public Resource loadFileAsResource(String fileName) {
        try {
            Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
            Resource resource = new UrlResource(filePath.toUri());
            
            if(resource.exists()) {
                return resource;
            } else {
                throw new FileNotFoundException("File not found: " + fileName);
            }
        } catch (MalformedURLException | FileNotFoundException ex) {
            throw new FileNotFoundException("File not found: " + fileName);
        }
    }
    
    public List<String> listAllFiles() {
        try {
            return Files.list(this.fileStorageLocation)
                    .map(Path::getFileName)
                    .map(Path::toString)
                    .collect(Collectors.toList());
        } catch (IOException ex) {
            throw new RuntimeException("Could not list files", ex);
        }
    }
    
    public void deleteFile(String fileName) {
        try {
            Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
            Files.deleteIfExists(filePath);
        } catch (IOException ex) {
            throw new RuntimeException("Could not delete file: " + fileName, ex);
        }
    }
}

public class FileStorageException extends RuntimeException {
    public FileStorageException(String message) {
        super(message);
    }
    
    public FileStorageException(String message, Throwable cause) {
        super(message, cause);
    }
}

REST Controller

@RestController
@RequestMapping("/api/files")
public class FileController {
    
    private final FileStorageService fileStorageService;
    
    @Autowired
    public FileController(FileStorageService fileStorageService) {
        this.fileStorageService = fileStorageService;
    }
    
    @PostMapping("/upload")
    public ResponseEntity<FileResponse> uploadFile(@RequestParam("file") MultipartFile file) {
        String fileName = fileStorageService.storeFile(file);
        
        FileResponse response = new FileResponse(
            fileName, 
            file.getContentType(), 
            file.getSize()
        );
        
        return ResponseEntity.ok(response);
    }
    
    @GetMapping("/download/{fileName:.+}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, 
                                               HttpServletRequest request) {
        // Load file as Resource
        Resource resource = fileStorageService.loadFileAsResource(fileName);
        
        // Try to determine file's content type
        String contentType = null;
        try {
            contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
        } catch (IOException ex) {
            // Fallback to default content type
            contentType = "application/octet-stream";
        }
        
        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(contentType))
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                        "attachment; filename=\"" + resource.getFilename() + "\"")
                .body(resource);
    }
    
    @GetMapping("/list")
    public ResponseEntity<List<String>> listFiles() {
        return ResponseEntity.ok(fileStorageService.listAllFiles());
    }
    
    @DeleteMapping("/delete/{fileName:.+}")
    public ResponseEntity<Void> deleteFile(@PathVariable String fileName) {
        fileStorageService.deleteFile(fileName);
        return ResponseEntity.noContent().build();
    }
}

// Response DTO
public class FileResponse {
    private String fileName;
    private String fileType;
    private long size;
    
    // constructor, getters, setters
}

Error Handling

Create a global exception handler:

@ControllerAdvice
public class FileUploadExceptionAdvice {
    
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<ErrorResponse> handleMaxSizeException(
            MaxUploadSizeExceededException exc) {
        return ResponseEntity
                .status(HttpStatus.PAYLOAD_TOO_LARGE)
                .body(new ErrorResponse("File too large!"));
    }
    
    @ExceptionHandler(FileStorageException.class)
    public ResponseEntity<ErrorResponse> handleStorageException(FileStorageException exc) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse(exc.getMessage()));
    }
    
    @ExceptionHandler(FileNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleFileNotFound(FileNotFoundException exc) {
        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(new ErrorResponse(exc.getMessage()));
    }
}

public class ErrorResponse {
    private String message;
    
    // constructor, getters, setters
}

Security Considerations

If you add Spring Security, configure CORS and CSRF:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors().and()
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/api/files/**").permitAll()
                .anyRequest().authenticated();
    }
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Angular Frontend

Angular Project Setup

Install necessary packages:

npm install @angular/material @angular/cdk @angular/animations
ng add @angular/material

File Service

Create a file service to communicate with the backend:

import { Injectable } from '@angular/core';
import { HttpClient, HttpEvent, HttpRequest, HttpEventType } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class FileService {
  private baseUrl = 'http://localhost:8080/api/files';

  constructor(private http: HttpClient) { }

  upload(file: File): Observable<HttpEvent<any>> {
    const formData: FormData = new FormData();
    formData.append('file', file);

    const req = new HttpRequest('POST', `${this.baseUrl}/upload`, formData, {
      reportProgress: true,
      responseType: 'json'
    });

    return this.http.request(req);
  }

  download(fileName: string): Observable<Blob> {
    return this.http.get(`${this.baseUrl}/download/${fileName}`, {
      responseType: 'blob'
    });
  }

  listFiles(): Observable<string[]> {
    return this.http.get<string[]>(`${this.baseUrl}/list`);
  }

  deleteFile(fileName: string): Observable<void> {
    return this.http.delete<void>(`${this.baseUrl}/delete/${fileName}`);
  }
}

Upload Component

import { Component } from '@angular/core';
import { FileService } from './file.service';

@Component({
  selector: 'app-upload',
  templateUrl: './upload.component.html',
  styleUrls: ['./upload.component.css']
})
export class UploadComponent {
  selectedFiles?: FileList;
  currentFile?: File;
  progress = 0;
  message = '';
  fileInfos?: Observable<any>;

  constructor(private fileService: FileService) { }

  selectFile(event: any): void {
    this.selectedFiles = event.target.files;
  }

  upload(): void {
    this.progress = 0;
    
    if (this.selectedFiles) {
      const file: File | null = this.selectedFiles.item(0);
      
      if (file) {
        this.currentFile = file;
        
        this.fileService.upload(this.currentFile).subscribe(
          (event: any) => {
            if (event.type === HttpEventType.UploadProgress) {
              this.progress = Math.round(100 * event.loaded / event.total);
            } else if (event instanceof HttpResponse) {
              this.message = event.body.message;
              this.fileInfos = this.fileService.getFiles();
            }
          },
          (err: any) => {
            console.log(err);
            this.progress = 0;
            
            if (err.error && err.error.message) {
              this.message = err.error.message;
            } else {
              this.message = 'Could not upload the file!';
            }
            
            this.currentFile = undefined;
          });
      }
      
      this.selectedFiles = undefined;
    }
  }
}

Upload component HTML:

<div class="upload-container">
  <mat-card>
    <mat-card-header>
      <mat-card-title>File Upload</mat-card-title>
    </mat-card-header>
    <mat-card-content>
      <div class="file-upload">
        <button mat-raised-button color="primary" (click)="fileInput.click()">
          Choose File
        </button>
        <input #fileInput type="file" (change)="selectFile($event)" style="display:none"/>
        <span class="file-name" *ngIf="currentFile">
          {{ currentFile.name }}
        </span>
      </div>
      
      <button mat-raised-button color="accent" [disabled]="!currentFile" (click)="upload()">
        Upload
      </button>
      
      <div *ngIf="progress > 0" class="progress-container">
        <mat-progress-bar mode="determinate" [value]="progress"></mat-progress-bar>
        <span>{{ progress }}%</span>
      </div>
      
      <div *ngIf="message" class="message">
        {{ message }}
      </div>
    </mat-card-content>
  </mat-card>
</div>

Download Component

import { Component, OnInit } from '@angular/core';
import { FileService } from '../file.service';

@Component({
  selector: 'app-download',
  templateUrl: './download.component.html',
  styleUrls: ['./download.component.css']
})
export class DownloadComponent implements OnInit {
  fileList: string[] = [];

  constructor(private fileService: FileService) { }

  ngOnInit(): void {
    this.loadFiles();
  }

  loadFiles(): void {
    this.fileService.listFiles().subscribe(
      files => {
        this.fileList = files;
      },
      error => {
        console.error('Error loading files:', error);
      }
    );
  }

  downloadFile(fileName: string): void {
    this.fileService.download(fileName).subscribe(
      (blob: Blob) => {
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = fileName;
        document.body.appendChild(a);
        a.click();
        window.URL.revokeObjectURL(url);
        document.body.removeChild(a);
      },
      error => {
        console.error('Error downloading file:', error);
      }
    );
  }

  deleteFile(fileName: string): void {
    if (confirm(`Are you sure you want to delete ${fileName}?`)) {
      this.fileService.deleteFile(fileName).subscribe(
        () => {
          this.loadFiles(); // Refresh the list
        },
        error => {
          console.error('Error deleting file:', error);
        }
      );
    }
  }
}

Download component HTML:

<div class="download-container">
  <mat-card>
    <mat-card-header>
      <mat-card-title>Available Files</mat-card-title>
    </mat-card-header>
    <mat-card-content>
      <mat-list>
        <mat-list-item *ngFor="let file of fileList">
          <span class="file-name">{{ file }}</span>
          <button mat-icon-button color="primary" (click)="downloadFile(file)">
            <mat-icon>download</mat-icon>
          </button>
          <button mat-icon-button color="warn" (click)="deleteFile(file)">
            <mat-icon>delete</mat-icon>
          </button>
        </mat-list-item>
      </mat-list>
      
      <div *ngIf="fileList.length === 0" class="no-files">
        No files available for download.
      </div>
    </mat-card-content>
  </mat-card>
</div>

Progress Tracking

Enhance the upload component with detailed progress tracking:

// Add to upload component
uploadStatus: 'ready' | 'uploading' | 'success' | 'error' = 'ready';
uploadedBytes = 0;
totalBytes = 0;
speed = 0;
timeRemaining = 0;

private calculateSpeedAndTime(startTime: number, loaded: number, total: number) {
  const timeElapsed = (Date.now() - startTime) / 1000; // in seconds
  this.speed = loaded / timeElapsed; // bytes per second
  
  if (this.speed > 0) {
    this.timeRemaining = (total - loaded) / this.speed;
  }
}

upload(): void {
  if (!this.selectedFiles) return;
  
  const file = this.selectedFiles.item(0);
  if (!file) return;
  
  this.currentFile = file;
  this.uploadStatus = 'uploading';
  this.message = '';
  this.progress = 0;
  this.totalBytes = file.size;
  this.uploadedBytes = 0;
  
  const startTime = Date.now();
  
  this.fileService.upload(this.currentFile).subscribe(
    (event: any) => {
      if (event.type === HttpEventType.UploadProgress) {
        this.uploadedBytes = event.loaded;
        this.totalBytes = event.total || this.totalBytes;
        this.progress = Math.round(100 * this.uploadedBytes / this.totalBytes);
        this.calculateSpeedAndTime(startTime, this.uploadedBytes, this.totalBytes);
      } else if (event instanceof HttpResponse) {
        this.uploadStatus = 'success';
        this.message = 'File uploaded successfully!';
        this.loadFiles(); // Refresh file list
      }
    },
    (err: any) => {
      this.uploadStatus = 'error';
      this.progress = 0;
      this.message = err.error?.message || 'Could not upload the file!';
    }
  );
  
  this.selectedFiles = undefined;
}

Enhanced progress display in HTML:

<div *ngIf="uploadStatus === 'uploading'" class="upload-details">
  <div>Progress: {{ progress }}%</div>
  <div>Uploaded: {{ uploadedBytes | fileSize }} of {{ totalBytes | fileSize }}</div>
  <div>Speed: {{ speed | fileSize }}/s</div>
  <div>Time remaining: ~{{ timeRemaining | number:'1.0-0' }}s</div>
</div>

Create a file size pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'fileSize'
})
export class FileSizePipe implements PipeTransform {
  transform(bytes: number, decimals: number = 2): string {
    if (bytes === 0) return '0 Bytes';
    
    const k = 1024;
    const dm = decimals < 0 ? 0 : decimals;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm) + ' ' + sizes[i];
  }
}

Advanced Features

1. Multiple File Upload

Modify the backend controller:

@PostMapping("/upload-multiple")
public ResponseEntity<List<FileResponse>> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
    List<FileResponse> responses = Arrays.stream(files)
            .map(file -> {
                String fileName = fileStorageService.storeFile(file);
                return new FileResponse(
                    fileName, 
                    file.getContentType(), 
                    file.getSize()
                );
            })
            .collect(Collectors.toList());
    
    return ResponseEntity.ok(responses);
}

Frontend service:

uploadMultiple(files: FileList): Observable<HttpEvent<any>> {
  const formData: FormData = new FormData();
  Array.from(files).forEach(file => {
    formData.append('files', file);
  });

  const req = new HttpRequest('POST', `${this.baseUrl}/upload-multiple`, formData, {
    reportProgress: true,
    responseType: 'json'
  });

  return this.http.request(req);
}

2. File Type Validation

Backend validation:

@PostMapping("/upload")
public ResponseEntity<FileResponse> uploadFile(
        @RequestParam("file") @ValidFileType(allowedTypes = {"image/jpeg", "application/pdf"}) 
        MultipartFile file) {
    // ...
}

// Custom validation annotation
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidFileTypeValidator.class)
public @interface ValidFileType {
    String message() default "Invalid file type";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String[] allowedTypes() default {};
}

public class ValidFileTypeValidator implements ConstraintValidator<ValidFileType, MultipartFile> {
    private String[] allowedTypes;
    
    @Override
    public void initialize(ValidFileType constraintAnnotation) {
        this.allowedTypes = constraintAnnotation.allowedTypes();
    }
    
    @Override
    public boolean isValid(MultipartFile file, ConstraintValidatorContext context) {
        if (file.isEmpty()) return false;
        
        String fileType = file.getContentType();
        if (fileType == null) return false;
        
        return Arrays.asList(allowedTypes).contains(fileType);
    }
}

Frontend validation:

// In upload component
allowedTypes = ['image/jpeg', 'application/pdf'];

isFileTypeValid(file: File): boolean {
  return this.allowedTypes.includes(file.type);
}

upload(): void {
  if (!this.selectedFiles) return;
  
  const file = this.selectedFiles.item(0);
  if (!file) return;
  
  if (!this.isFileTypeValid(file)) {
    this.message = 'Invalid file type. Only JPEG images and PDFs are allowed.';
    return;
  }
  
  // Proceed with upload
}

3. Database Storage

@Entity
public class FileEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String fileName;
    private String fileType;
    private long size;
    private LocalDateTime uploadDate;
    
    @Lob
    private byte[] data;
    
    // constructors, getters, setters
}

Repository:

public interface FileRepository extends JpaRepository<FileEntity, Long> {
    Optional<FileEntity> findByFileName(String fileName);
}

Modified storage service:

@Service
public class DatabaseFileStorageService {
    
    private final FileRepository fileRepository;
    
    @Autowired
    public DatabaseFileStorageService(FileRepository fileRepository) {
        this.fileRepository = fileRepository;
    }
    
    public FileEntity storeFile(MultipartFile file) throws IOException {
        String fileName = StringUtils.cleanPath(file.getOriginalFilename());
        
        FileEntity fileEntity = new FileEntity();
        fileEntity.setFileName(fileName);
        fileEntity.setFileType(file.getContentType());
        fileEntity.setSize(file.getSize());
        fileEntity.setUploadDate(LocalDateTime.now());
        fileEntity.setData(file.getBytes());
        
        return fileRepository.save(fileEntity);
    }
    
    public FileEntity getFile(Long fileId) {
        return fileRepository.findById(fileId)
                .orElseThrow(() -> new FileNotFoundException("File not found with id " + fileId));
    }
    
    public Stream<FileEntity> getAllFiles() {
        return fileRepository.findAll().stream();
    }
}

4. Chunked Uploads for Large Files

Backend controller:

@PostMapping("/upload-chunk")
public ResponseEntity<String> uploadChunk(
        @RequestParam("file") MultipartFile file,
        @RequestParam("chunkNumber") int chunkNumber,
        @RequestParam("totalChunks") int totalChunks,
        @RequestParam("originalFileName") String originalFileName) {
    
    // Create a temporary directory for chunks
    Path chunkDir = Paths.get("temp-chunks", originalFileName);
    try {
        Files.createDirectories(chunkDir);
    } catch (IOException e) {
        throw new RuntimeException("Could not create chunk directory", e);
    }
    
    // Save the chunk
    Path chunkPath = chunkDir.resolve(String.valueOf(chunkNumber));
    try {
        file.transferTo(chunkPath);
    } catch (IOException e) {
        throw new RuntimeException("Could not save chunk", e);
    }
    
    // If this was the last chunk, combine all chunks
    if (chunkNumber == totalChunks - 1) {
        try {
            Path outputPath = fileStorageLocation.resolve(originalFileName);
            Files.createFile(outputPath);
            
            for (int i = 0; i < totalChunks; i++) {
                Path currentChunk = chunkDir.resolve(String.valueOf(i));
                Files.write(outputPath, Files.readAllBytes(currentChunk), StandardOpenOption.APPEND);
                Files.delete(currentChunk);
            }
            
            Files.delete(chunkDir);
        } catch (IOException e) {
            throw new RuntimeException("Could not combine chunks", e);
        }
    }
    
    return ResponseEntity.ok("Chunk uploaded successfully");
}

Frontend chunked upload service:

const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB

uploadInChunks(file: File): Observable<any> {
  return new Observable(observer => {
    const chunks = Math.ceil(file.size / CHUNK_SIZE);
    const fileReader = new FileReader();
    let currentChunk = 0;
    
    fileReader.onload = (e) => {
      const chunkData = e.target?.result as ArrayBuffer;
      const formData = new FormData();
      const blob = new Blob([new Uint8Array(chunkData)]);
      
      formData.append('file', blob, file.name);
      formData.append('chunkNumber', currentChunk.toString());
      formData.append('totalChunks', chunks.toString());
      formData.append('originalFileName', file.name);
      
      this.http.post(`${this.baseUrl}/upload-chunk`, formData).subscribe(
        () => {
          currentChunk++;
          if (currentChunk < chunks) {
            this.readNextChunk(file, fileReader, currentChunk, CHUNK_SIZE);
          } else {
            observer.next({ message: 'File uploaded successfully' });
            observer.complete();
          }
        },
        err => {
          observer.error(err);
        }
      );
    };
    
    this.readNextChunk(file, fileReader, currentChunk, CHUNK_SIZE);
  });
}

private readNextChunk(file: File, fileReader: FileReader, chunkNumber: number, chunkSize: number) {
  const start = chunkNumber * chunkSize;
  const end = Math.min(file.size, start + chunkSize);
  const slice = file.slice(start, end);
  fileReader.readAsArrayBuffer(slice);
}

Deployment Considerations

Backend

  1. File Storage:
    • For production, use absolute paths for file storage
    • Consider cloud storage (AWS S3, Google Cloud Storage, Azure Blob Storage)
  2. Security:
    • Implement proper authentication/authorization
    • Validate file names and content
    • Scan for viruses/malware
  3. Performance:
    • Add rate limiting
    • Consider asynchronous processing for large files
  4. Configuration:propertiesCopyDownload# Production properties file.upload-dir=/var/uploads spring.servlet.multipart.max-file-size=50MB spring.servlet.multipart.max-request-size=50MB

Frontend

  1. Environment Configuration:typescriptCopyDownload// environment.prod.ts export const environment = { production: true, apiUrl: 'https://your-api-domain.com/api' };
  2. Build Optimization:bashCopyDownloadng build --prod
  3. CORS:
    • Ensure your production API has the correct CORS settings
    • Or use a proxy in production
  4. Error Handling:
    • Add more robust error handling and user notifications
    • Implement retry logic for failed uploads

Conclusion

This comprehensive guide covered building a complete file upload and download system with Spring Boot and Angular, including:

  1. Basic single file upload/download
  2. Progress tracking
  3. Multiple file uploads
  4. File type validation
  5. Database storage option
  6. Chunked uploads for large files
  7. Deployment considerations

You can extend this further by adding:

  • File previews (for images/PDFs)
  • Metadata editing
  • File sharing functionality
  • Advanced search and filtering
  • Integration with cloud storage providers

Remember to always implement proper security measures when handling file uploads in production applications.

Leave a Reply

Your email address will not be published. Required fields are marked *