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
-
Open Spring Initializr:
- Go to Spring Initializr in your web browser.
-
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
.
-
Select Dependencies:
- On the
Dependencies
screen, select:- Spring Web
- Spring Boot DevTools
- Click
Next
.
- On the
-
Generate the Project:
- Click
Generate
to download the project zip file. - Extract the zip file to your desired location.
- Click
-
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 onlocalhost: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
- Open a terminal and run the following command to create a new Angular project:
ng new file-client
- 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
Post a Comment
Leave Comment