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:
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:
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.
{{ 'Comments (%count%)' | trans {count:count} }}
{{ 'Comments are closed.' | trans }}