DEV Community

Cover image for Spring Boot Meets Firebase: My Journey of Building a File Upload System🚀
Priya
Priya

Posted on

Spring Boot Meets Firebase: My Journey of Building a File Upload System🚀

Recently, I created a REST API for uploading and retrieving files using Spring Boot and Firebase. This project was driven by my interest after seeing someone mention they were having issues with file uploads in Spring Boot. In an effort to help, I decided to familiarize myself with file uploads using Spring Boot and Firebase.

So, if you’re like me and want to get familiar with file management using Firebase and Spring Boot, let’s get started! 😊


First Up: Setting Up Firebase 🛠️

Before we dive into the code, the first step is setting up Firebase to handle file uploads.

Step 1: Create a Firebase Project

  • Go to the Firebase Console: Firebase Console
  • Create a New Project:
    • Enter a project name.
    • Click "Continue" and select whether to enable Google Analytics (optional).
    • Click "Create Project."

Step 2: Enable Cloud Storage

  • Open Your Project: In the Firebase console, select your project.
  • Navigate to Cloud Storage:
    • Click on “Build > Storage” in the left sidebar.
    • Click on “Get Started” and "Start in test mode (or production if needed)" to enable Cloud Storage for your project.

Step 3: Get Your Service Account Key 🔑

Service Account Key

  • Go to "Project Settings" > "Service accounts."
  • Click on “Generate new private key,” and a JSON file will be downloaded. Keep this file secure.

That’s it for Firebase setup! Now, let’s move on to integrating it with Spring Boot.


Next: Setting Up Spring Boot ⚙️

1. Create a Spring Boot Project

You can create a Spring Boot project using Spring Initializr.

Select the following dependencies:

  • Spring Web: For building REST APIs.
  • Spring Boot DevTools (optional, for easier development).

2. Add Firebase Dependencies

Add the Firebase dependency to your pom.xml:

<dependency>
    <groupId>com.google.firebase</groupId>
    <artifactId>firebase-admin</artifactId>
    <version>8.1.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

3. Firebase Configuration

You need to set up Firebase authentication using the .json key file you downloaded earlier.

  • Place the .json file in your src/main/resources directory.
  • Then, create a configuration class FirebaseConfig to initialize Firebase:
@Configuration
public class FirebaseConfig {

    @Bean
    FirebaseApp firebaseApp() throws IOException {
        ClassPathResource resource = new ClassPathResource("serviceAccountKey.json"); 
        InputStream serviceAccount = resource.getInputStream();

        FirebaseOptions options = FirebaseOptions.builder()
            .setProjectId("fir-fileuploadapi") 
            .setCredentials(GoogleCredentials.fromStream(serviceAccount))
            .setStorageBucket("fir-fileuploadapi.appspot.com") 
            .setDatabaseUrl("https://fir-fileuploadapi-default-rtdb.firebaseio.com")
            .build();

        return FirebaseApp.initializeApp(options);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, replace the projectId, bucketName, and databaseUrl with your own details from Firebase.

Where to get those?

  • Project ID: Firebase console > Your project (that you created earlier) > Project settings.

Project ID

  • Bucket Name: Firebase console > Your project > Build > Storage

Bucket Name

  • Database URL: Firebase console > Your project > Build > Realtime Database > Create Database

Database URL

Then, your database URL will be shown at the top.

Database URL Top

4. Create a Service

Create a FileStorageService class with a constructor.

@Service
public class FileStorageService {

    private final Storage storage;
    private final FileMetaDataRepository repo;
    private final String bucketName = "fir-fileuploadapi.appspot.com";

    public FileStorageService(FileMetaDataRepository repo) throws IOException {
        this.repo = repo;

        ClassPathResource resource = new ClassPathResource("serviceAccountKey.json");
        InputStream serviceAccount = resource.getInputStream();

        GoogleCredentials credentials = GoogleCredentials.fromStream(serviceAccount)
                .createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"));

        this.storage = StorageOptions.newBuilder().setCredentials(credentials).build().getService();
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, just replace serviceAccountKey.json with the name of the JSON file stored in the src/main/resources folder and the Bucket Name you got earlier.

Wait! What is FileMetaDataRepository? We haven't created that yet.

Here you go!

@Repository
public interface FileMetaDataRepository extends JpaRepository<FileMetaData, Integer> {
    FileMetaData findByUniqueId(String uniqueId);
}
Enter fullscreen mode Exit fullscreen mode

And the corresponding FileMetaData.java:

@Entity
@Table(name = "files")
public class FileMetaData {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "unique_id", nullable = false)
    private String uniqueId;

    @Column(name = "object_name", nullable = false)
    private String objectName;

    @Column(name = "upload_date", nullable = false)
    private LocalDateTime uploadDate;

    public FileMetaData() {}

    public FileMetaData(Integer id, String uniqueId, String objectName, LocalDateTime uploadDate) {
        this.id = id;
        this.uniqueId = uniqueId;
        this.objectName = objectName;
        this.uploadDate = uploadDate;
    }
    // getters and setters go here...
}
Enter fullscreen mode Exit fullscreen mode

What’s the need for this?

FileMetaData is an object that holds metadata (data about the data) about the file we are storing. It's just for making our retrieval smoother!

5. Create the Uploading and Retrieving Methods

Uploading Files

In the FileStorageService, we’ll create a method for uploading files:

public String uploadFile(MultipartFile file) throws IOException {
    if (file.isEmpty()) {
        throw new IllegalArgumentException("File is empty. Please upload a valid file.");
    }

    String uniqueID = UUID.randomUUID().toString();
    String objectName = uniqueID + "_" + file.getOriginalFilename();

    BlobId blobId = BlobId.of(bucketName, objectName);

    BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build();

    storage.create(blobInfo, file.getBytes());

    FileMetaData metaData = new FileMetaData();
    metaData.setUniqueId(uniqueID);
    metaData.setObjectName(objectName);
    metaData.setUploadDate(LocalDateTime.now());

    repo.save(metaData);

    return uniqueID;
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We first check if the uploaded file is empty. If it is, we throw an exception.
  • We generate a unique ID for the file using UUID to ensure no two files have the same identifier.
  • We create a BlobId and BlobInfo for the file, then store the file in Firebase using the storage.create() method.
  • Finally, we save the file metadata in our repository for easy retrieval later.

Retrieving Files

Now, let’s create a method to retrieve files by their unique ID:

public FileResponse retrieveFile(String fileId) {
    FileMetaData fileMetadata = repo.findByUniqueId(fileId);

    if (fileMetadata == null) {
        throw new IllegalArgumentException("No file found with the given ID: " + fileId);
    }

    String objectName = fileMetadata.getObjectName();
    BlobId blobId = BlobId.of(bucketName, objectName);
    Blob blob = storage.get(blobId);

    if (blob == null || !blob.exists()) {
        throw new IllegalArgumentException("No file found with the given ID: " + fileId);
    }

    FileResponse fileResponse = new FileResponse(objectName, blob.getContent());
    return fileResponse; 
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We look up the file metadata by its unique ID. If it doesn't exist, we throw an exception.
  • We retrieve the Blob from Firebase using the object name stored in our metadata.
  • If the blob exists, we create a FileResponse object that contains the file name and its content, ready to be returned.

Here is how the FileResponse class looks like,

public class FileResponse {
    private String fileName;
    private byte[] fileContent;

    public FileResponse(String fileName, byte[] fileContent) {
        this.fileName = fileName;
        this.fileContent = fileContent;
    }

    public String getFileName() {
        return fileName;
    }

    public byte[] getFileContent() {
        return fileContent;
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Create a Controller

Next, we’ll set up a controller to handle our HTTP requests:

@RestController
@RequestMapping("/api/files")
public class FileStorageController {

    @Autowired
    private FileStorageService fileStorageService;

    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(@RequestParam MultipartFile file) {
        try {
            String fileId = fileStorageService.uploadFile(file

);
            return ResponseEntity.ok(fileId);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }
    }

    @GetMapping("/retrieve/{fileId}")
    public ResponseEntity<FileResponse> retrieveFile(@PathVariable String fileId) {
        try {
            FileResponse fileResponse = fileStorageService.retrieveFile(fileId);
            return ResponseEntity.ok(fileResponse);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The uploadFile method handles file uploads by calling our service method and returning the unique ID.
  • The retrieveFile method fetches the file using the unique ID and returns its content.

Testing the API 🧪

Now that everything is set up, you can test your API using Postman or any other tool.

  • To Upload a File:

    • Make a POST request to http://localhost:8080/api/files/upload with the file in the form-data.
  • To Retrieve a File:

    • Make a GET request to http://localhost:8080/api/files/retrieve/{fileId} using the unique ID you received from the upload response.

Conclusion

And there you have it! You’ve successfully created a Spring Boot REST API to upload and retrieve files using Firebase. I hope this guide was helpful and easy to follow! If you have any questions or need further assistance, feel free to drop a comment below!

Happy coding! 💻

Top comments (0)