Angular Spring Boot File Upload and Download Example

In this tutorial, we will create a file upload and download application using Spring Boot 3.3 for the backend and Angular 18 for the frontend. We will handle CORS issues to ensure smooth communication between the Angular frontend and the Spring Boot backend.

Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed
  • Apache Maven installed
  • Node.js and npm installed
  • Angular CLI installed (npm install -g @angular/cli)
  • An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed

Step 1: Setting Up the Spring Boot Backend

1.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3.3
    • Group: com.example
    • Artifact: file-service
    • Name: file-service
    • Description: File Upload and Download Service
    • Package Name: com.example.fileservice
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select:
      • Spring Web
      • Spring Boot DevTools
    • Click Next.
  4. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  5. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

1.2 Update application.properties

Open the application.properties file located in the src/main/resources directory and add the following configuration:

server.port=8080
file.upload-dir=./uploads

Explanation:

  • Configures the server port to 8080.
  • Sets the directory where uploaded files will be stored.

1.3 Create File Storage Service

Create a FileStorageService class in the com.example.fileservice.service package:

package com.example.fileservice.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

@Service
public class FileStorageService {

    private final Path fileStorageLocation;

    public FileStorageService(@Value("${file.upload-dir}") String uploadDir) {
        this.fileStorageLocation = Paths.get(uploadDir)
                .toAbsolutePath().normalize();
        try {
            Files.createDirectories(this.fileStorageLocation);
        } catch (Exception ex) {
            throw new RuntimeException("Could not create the directory where the uploaded files will be stored.", ex);
        }
    }

    public String storeFile(MultipartFile file) {
        String fileName = StringUtils.cleanPath(file.getOriginalFilename());

        try {
            if (fileName.contains("..")) {
                throw new RuntimeException("Sorry! Filename contains invalid path sequence " + fileName);
            }

            Path targetLocation = this.fileStorageLocation.resolve(fileName);
            Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);

            return fileName;
        } catch (IOException ex) {
            throw new RuntimeException("Could not store file " + fileName + ". Please try again!", ex);
        }
    }

    public Path loadFileAsResource(String fileName) {
        return this.fileStorageLocation.resolve(fileName).normalize();
    }
}

Explanation:

  • @Service: Marks the class as a service component.
  • storeFile(MultipartFile file): Saves the uploaded file to the configured directory.
  • loadFileAsResource(String fileName): Loads the file as a resource for download.

1.4 Create File Controller

Create a FileController class in the com.example.fileservice.controller package:

package com.example.fileservice.controller;

import com.example.fileservice.service.FileStorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
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 javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;

@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/api/files")
public class FileController {

    private final FileStorageService fileStorageService;

    @Autowired
    public FileController(FileStorageService fileStorageService) {
        this.fileStorageService = fileStorageService;
    }

    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
        String fileName = fileStorageService.storeFile(file);
        String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
                .path("/api/files/download/")
                .path(fileName)
                .toUriString();

        return ResponseEntity.ok(fileDownloadUri);
    }

    @GetMapping("/download/{fileName:.+}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
        Path filePath = fileStorageService.loadFileAsResource(fileName);
        Resource resource;
        try {
            resource = filePath.toUri().toURL().openStream()::transferTo;
        } catch (IOException ex) {
            throw new RuntimeException("File not found " + fileName, ex);
        }

        String contentType;
        try {
            contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
        } catch (IOException ex) {
            contentType = "application/octet-stream";
        }

        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(Objects.requireNonNullElse(contentType, "application/octet-stream")))
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
                .body(resource);
    }
}

Explanation:

  • @CrossOrigin(origins = "http://localhost:4200"): Enables CORS for requests from the Angular frontend running on localhost:4200.
  • uploadFile(@RequestParam("file") MultipartFile file): Handles file upload and returns the file download URI.
  • downloadFile(@PathVariable String fileName, HttpServletRequest request): Handles file download.

1.5 Run the Spring Boot Application

Run the application by executing the FileServiceApplication class. The backend should be up and running on http://localhost:8080.

Step 2: Setting Up the Angular Frontend

2.1 Create an Angular Project

  1. Open a terminal and run the following command to create a new Angular project:
ng new file-client
  1. Navigate to the project directory:
cd file-client

2.2 Install Dependencies

Install Bootstrap for styling:

npm install bootstrap

Add Bootstrap to angular.json:

"styles": [
  "src/styles.css",
  "node_modules/bootstrap/dist/css/bootstrap.min.css"
],

2.3 Create Angular Services and Components

2.3.1 Create File Service

Generate the FileService:

ng generate service services/file

Edit file.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class FileService {

  private baseUrl = 'http://localhost:8080/api/files';

  constructor(private http: HttpClient) { }

  upload(file: File): Observable<any> {
    const formData: FormData = new FormData();
    formData.append('file', file);
    return this.http.post(`${this.baseUrl}/upload`, formData, { responseType: 'text' });
  }

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

Explanation:

  • @Injectable({ providedIn: 'root' }): Marks the service as injectable and available throughout the app.
  • HttpClient: Service for making HTTP requests.
  • upload(file: File): Sends a POST request to upload a file.
  • download(fileName: string): Sends a GET request to download a file.

2.3.2 Create Components

Generate the components for file upload and download:

ng generate component components/upload
ng generate component components/download

Edit upload.component.ts:

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

@Component({
  selector: 'app-upload',
  templateUrl: './upload.component.html',
  styleUrls: ['./upload.component.css']
})
export class UploadComponent {
  selectedFile?: File;
  message = '';

  constructor(private fileService: FileService) {

 }

  onFileSelected(event: any) {
    this.selectedFile = event.target.files[0];
  }

  uploadFile() {
    if (this.selectedFile) {
      this.fileService.upload(this.selectedFile).subscribe(response => {
        this.message = `File uploaded successfully: ${response}`;
      }, error => {
        console.error('Upload error: ', error);
        this.message = 'Could not upload the file';
      });
    }
  }
}

Edit upload.component.html:

<div class="container mt-5">
  <div class="row justify-content-center">
    <div class="col-md-6">
      <div class="card">
        <div class="card-header">Upload File</div>
        <div class="card-body">
          <div class="form-group">
            <input type="file" (change)="onFileSelected($event)" class="form-control-file">
          </div>
          <button (click)="uploadFile()" class="btn btn-primary">Upload</button>
          <div class="mt-3" *ngIf="message">{{ message }}</div>
        </div>
      </div>
    </div>
  </div>
</div>

Edit download.component.ts:

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

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

  constructor(private fileService: FileService) { }

  downloadFile() {
    this.fileService.download(this.fileName).subscribe(response => {
      const blob = new Blob([response], { type: 'application/octet-stream' });
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = this.fileName;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      this.message = `File downloaded successfully`;
    }, error => {
      console.error('Download error: ', error);
      this.message = 'Could not download the file';
    });
  }
}

Edit download.component.html:

<div class="container mt-5">
  <div class="row justify-content-center">
    <div class="col-md-6">
      <div class="card">
        <div class="card-header">Download File</div>
        <div class="card-body">
          <div class="form-group">
            <input type="text" [(ngModel)]="fileName" class="form-control" placeholder="Enter filename to download">
          </div>
          <button (click)="downloadFile()" class="btn btn-primary">Download</button>
          <div class="mt-3" *ngIf="message">{{ message }}</div>
        </div>
      </div>
    </div>
  </div>
</div>

2.4 Update Angular Routing

Edit app-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UploadComponent } from './components/upload/upload.component';
import { DownloadComponent } from './components/download/download.component';

const routes: Routes = [
  { path: 'upload', component: UploadComponent },
  { path: 'download', component: DownloadComponent },
  { path: '', redirectTo: '/upload', pathMatch: 'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Explanation:

  • Defines routes for the upload and download components.
  • Redirects the root path to the upload component.

2.5 Update Angular App Module

Edit app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UploadComponent } from './components/upload/upload.component';
import { DownloadComponent } from './components/download/download.component';

@NgModule({
  declarations: [
    AppComponent,
    UploadComponent,
    DownloadComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Explanation:

  • Imports necessary modules for the Angular app.
  • Declares the components used in the app.
  • Sets up the app's root module.

2.6 Run the Angular Application

Open a terminal in the Angular project directory and run the application:

ng serve

Visit http://localhost:4200 in your web browser to see the application.

Conclusion

In this tutorial, we created a file upload and download application using Spring Boot 3.3 for the backend and Angular 18 for the frontend. We handled CORS issues to ensure smooth communication between the Angular frontend and the Spring Boot backend. By following this structure, you can extend and customize the application as needed.

Comments