Spring Boot 2 + Angular 11 + Upload File Example

February 27, 2021 No comments Spring Boot Angular Upload File

1. Introduction

In this tutorial we are going to present how to create an application for uploading a single file into filesystem using Spring Boot 2 and Angular 11.

2. Architecture

Let's take a look at the architecture:

Spring boot upload single file

Here we have a general division into the backend and frontend side. The Angular in version 11 will be responsible for building the UI and the Spring Boot will handle uploaded files and store them into the filesystem. The communication between modules will be RESTful.

3. Spring Boot application

On the backend side, we have a Spring Boot application server that provides a single endpoint /files for handling multipart/form-data requests.

3.1. Project structure

The backend application will have the following structure:

├── main
│   ├── java
│   │   └── com
│   │       └── frontbackend
│   │           └── springboot
│   │               ├── Application.java
│   │               ├── controller
│   │               │   └── FilesController.java
│   │               ├── exceptions
│   │               │   ├── FileUploadException.java
│   │               │   └── RestExceptionHandler.java
│   │               ├── model
│   │               │   └── UploadResponseMessage.java
│   │               └── service
│   │                   └── FileService.java
│   └── resources
│       └── application.properties

Let's briefly explain used components:

  • Application - main Spring Boot class responsible for starting web container,
  • FilesController - Spring REST controller class for handling HTTP requests,
  • FileUploadException - base exception class,
  • RestExceptionHandler - class responsible for mapping exceptions into HTTP responses,
  • UploadResponseMessage - Java representation of JSON that provides information about uploading status,
  • FileService - service that stores uploaded files in the filesystem,
  • application.properties - Spring Boot application configuration file.

3.2. Rest Controller

The FilesController class is responsible for handling multipart requests.

package com.frontbackend.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.frontbackend.springboot.model.UploadResponseMessage;
import com.frontbackend.springboot.service.FileService;

@RestController
@RequestMapping("files")
public class FilesController {

    private final FileService fileService;

    @Autowired
    public FilesController(FileService fileService) {
        this.fileService = fileService;
    }

    @PostMapping
    public ResponseEntity<UploadResponseMessage> uploadFile(@RequestParam("file") MultipartFile file) {
        fileService.save(file);

        return ResponseEntity.status(HttpStatus.OK)
                             .body(new UploadResponseMessage("Uploaded the file successfully: " + file.getOriginalFilename()));
    }
}

In this class, we used the MultipartFile object that is a representation of an uploaded file received in a multipart request. When the user uploaded a file, at the first step it will be stored in a temporary location in the filesystem and the MultipartFile points to that file. This temporary file will be removed at the end of request processing.

3.3. Service class

The FileService is responsible for saving uploaded files in the filesystem:

package com.frontbackend.springboot.service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

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

import com.frontbackend.springboot.exceptions.FileUploadException;

@Service
public class FileService {

    @Value("${upload.path}")
    private String uploadPath;

    public void save(MultipartFile file) throws FileUploadException {
        try {
            Path root = Paths.get(uploadPath);
            Path resolve = root.resolve(file.getOriginalFilename());
            if (resolve.toFile()
                       .exists()) {
                throw new FileUploadException("File already exists: " + file.getOriginalFilename());
            }
            Files.copy(file.getInputStream(), resolve);
        } catch (Exception e) {
            throw new FileUploadException("Could not store the file. Error: " + e.getMessage());
        }
    }
}

Path, where we will store all uploaded files, is saved in application parameters in property upload.path. If a file with the same name exists in that location the new FileUploadException will be thrown.

3.4. Spring Boot application class

In the Spring Boot application class we configured CORS and upload size limits just to show it could be implemented here:

package com.frontbackend.springboot;

import javax.servlet.MultipartConfigElement;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.util.unit.DataSize;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize(DataSize.ofKilobytes(512));
        factory.setMaxRequestSize(DataSize.ofKilobytes(512));
        return factory.createMultipartConfig();
    }

    @Bean
    WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/files")
                        .allowedOrigins("http://localhost:4200");
            }
        };
    }
}

3.5. Application configuration file

In the application properties file we have a single entry with the path to the folder where we will save all files:


upload.path=/path/to/upload/folder

4. Angular application

To generate the basic structure of the Angular application we will use CLI:

We used the following command:

ng new angular
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS

Next, we would like to create a service and component for uploading files:

ng g s services/upload-file
ng g c components/upload-file

4.1. Web project structure

The generated Angular application project has the following structure:

├── app
│   ├── app.component.html
│   ├── app.component.ts
│   ├── app.module.ts
│   ├── components
│   │   └── upload-file
│   │       ├── upload-file.component.html
│   │       └── upload-file.component.ts
│   └── services
│       └── upload-file.service.ts
├── assets
├── environments
│   ├── environment.prod.ts
│   └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
└── styles.css

In this structure we could distinguished:

  • app.module - used to import necessary libraries and components,
  • upload-file.service - method to upload files in Spring Boot application server, used HTTPClient,
  • upload-files.component - the main website that contains upload form,
  • app.component - main application component,
  • index.html - base HTML website.

4.2. Upload file service

The UploadFileService will use the HTTPClient library to send multipart requests to the backend.

import { Injectable } from '@angular/core';
import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';

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

  constructor(private http: HttpClient) {
  }

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

    formData.append('file', file);

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

    return this.http.request(req);
  }
}

The component used FormData is a special data structure used to save key/value pairs. It uses the same format a form would use with the encoding set to multipart/form-data.

4.3. Upload file component

The UploadFileComponent will be our main component responsible for interactions with the end-user.

import { Component, OnInit } from '@angular/core';
import { UploadFileService } from '../../services/upload-file.service';
import { HttpEventType, HttpResponse } from '@angular/common/http';

@Component({
  selector: 'app-upload-file',
  templateUrl: './upload-file.component.html',
  styles: []
})
export class UploadFileComponent implements OnInit {

  selectedFiles?: FileList;
  currentFile?: File;
  message = '';
  errorMsg = '';

  constructor(private uploadService: UploadFileService) {
  }

  ngOnInit(): void {
  }

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

  upload(): void {
    this.errorMsg = '';

    if (this.selectedFiles) {
      const file: File | null = this.selectedFiles.item(0);

      if (file) {
        this.currentFile = file;

        this.uploadService.upload(this.currentFile).subscribe(
          (event: any) => {
            if (event.type === HttpEventType.UploadProgress) {
              console.log(Math.round(100 * event.loaded / event.total));

            } else if (event instanceof HttpResponse) {
              this.message = event.body.responseMessage;
            }
          },
          (err: any) => {
            console.log(err);

            if (err.error && err.error.responseMessage) {
              this.errorMsg = err.error.responseMessage;
            } else {
              this.errorMsg = 'Error occurred while uploading a file!';
            }

            this.currentFile = undefined;
          });
      }

      this.selectedFiles = undefined;
    }
  }
}

The selectFile method will be called then the user selects the file in the input field.

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

On upload file action the upload(): function will be called. It uses FileService to send multipart data to the backend side. When the parameter reportProgress: true, is set in HTTPClient we could present percent of uploaded file:

if (event.type === HttpEventType.UploadProgress) {
    console.log(Math.round(100 * event.loaded / event.total));
}

In component HTML we have a single input with special type="file". Button Upload file is used to send file data to Spring Boot server:

<div class="row">
    <div class="col-8">
        <label class="btn btn-default p-0">
            <input type="file" (change)="selectFile($event)"/>
        </label>
    </div>

    <div class="col-4">
        <button class="btn btn-success btn-sm" [disabled]="!selectedFiles" (click)="upload()">
            Upload file
        </button>
    </div>
</div>

<div *ngIf="message" class="alert alert-success" role="alert">{{ message }}</div>
<div *ngIf="errorMsg" class="alert alert-danger" role="alert">{{ errorMsg }}</div>

4.4. Application module

In Application module we import HTTPClientModule used in FileService and declare two components:

  • AppComponent,
  • UploadFileComponent.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { UploadFileComponent } from './components/upload-file/upload-file.component';
import { HttpClientModule } from '@angular/common/http';

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

5. Demo

The following screencast presents uploading file functionality:

Spring boot upload single file

6. Conclusion

In this tutorial, we presented how to build a simple application for uploading a file using Angular 11 on the frontend side and Spring Boot 2 on the backend.

As usual code used in this article is available in our GitHub repository.

{{ message }}

{{ 'Comments are closed.' | trans }}