Skip to main content
Image upload is a core feature that allows both couples and guests to contribute photos to wedding event galleries. Brautcloud handles multipart file uploads and stores images securely in AWS S3.

Upload overview

The upload system accepts image files from users and stores them in cloud storage while maintaining metadata in the database. Each uploaded image is associated with a specific wedding event.
Guests can upload photos without creating an account - they only need the event password to contribute their wedding photos.

Upload endpoint

The image upload endpoint accepts multipart form data with the image file and event ID.
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> uploadFile(@RequestPart("file") MultipartFile file,
        @RequestPart("eventId") String eventId) {
    ImageRequest request = new ImageRequest(Long.parseLong(eventId), file);
    return imageService.createNewImage(request);
}

Request structure

The upload request uses multipart form data with two parts:
file
MultipartFile
required
The image file to upload. Accepts common image formats like JPEG, PNG, etc.
eventId
string
required
The ID of the event to upload the image to

Example request

curl -X POST http://localhost:8080/api/image \
  -F "file=@wedding-photo.jpg" \
  -F "eventId=5"
Multipart form data is the standard way to upload files over HTTP. It allows sending both file data and metadata in a single request.

Upload processing flow

When a user uploads an image, the system processes it through several steps:

1. Create image record

First, the system creates a database record with image metadata:
public ResponseEntity<String> createNewImage(ImageRequest request) {
    Event event = eventRepository.findById(request.getEventId())
        .orElseThrow(() -> new RuntimeException("Event not found"));
    
    Image image = new Image();
    image.setVisible(true);
    image.setImageKey(request.getFile().getOriginalFilename());
    image.setEvent(event);
    
    imageRepository.save(image);
    
    return uploadFile(request.getFile());
}
This process:
  1. Validates the event exists
  2. Creates a new Image entity
  3. Sets visibility to true by default
  4. Uses the original filename as the S3 key
  5. Associates the image with the event
  6. Saves to database
  7. Proceeds to upload the file

2. Upload to S3

After creating the database record, the file is uploaded to AWS S3:
private ResponseEntity<String> uploadFile(MultipartFile file) {
    try {
        File tempFile = File.createTempFile("upload-", file.getOriginalFilename());
        file.transferTo(tempFile);

        s3Service.uploadFile(file.getOriginalFilename(), tempFile);

        return ResponseEntity.ok("File uploaded successfully");
    }
    catch (Exception e) {
        return ResponseEntity.status(500).body("Uploaded failed: " + e.getMessage());
    }
}
The S3 upload process:
  1. Create a temporary file on the server
  2. Transfer the multipart file data to the temp file
  3. Upload the temp file to S3 using the S3 service
  4. Return success or error response

3. S3 storage

The S3 service handles the actual cloud storage operation:
public void uploadFile(String key, File file) {
    PutObjectRequest request = PutObjectRequest.builder()
        .bucket(bucketName)
        .key(key)
        .build();

    s3Client.putObject(request, file.toPath());
}
The bucket name is configured in application properties (aws.s3.bucket). Ensure your AWS credentials have permissions to upload objects to this bucket.

Upload workflow

Here’s the complete upload flow from user action to stored image:

Image request DTO

The ImageRequest data transfer object encapsulates the upload data:
public class ImageRequest {
    private Long eventId;
    private MultipartFile file;
    
    public ImageRequest(Long eventId, MultipartFile file) {
        this.eventId = eventId;
        this.file = file;
    }
}

Properties

eventId
Long
required
The ID of the wedding event to upload to
file
MultipartFile
required
The Spring MultipartFile object containing the image data and metadata

File handling considerations

Temporary files

The upload process creates temporary files on the server to facilitate the S3 upload:
File tempFile = File.createTempFile("upload-", file.getOriginalFilename());
file.transferTo(tempFile);
In a production environment, ensure temporary files are cleaned up after upload. Consider implementing a cleanup mechanism or using try-with-resources for automatic cleanup.

File size limits

Spring Boot has default limits for multipart file uploads. You can configure these in application.properties:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
Adjust file size limits based on your needs. High-resolution wedding photos can be several megabytes each, so ensure your limits accommodate typical camera phone images.

Supported file types

While the current implementation accepts any file type, you should validate file types in production:
private boolean isValidImageType(MultipartFile file) {
    String contentType = file.getContentType();
    return contentType != null && 
           (contentType.equals("image/jpeg") ||
            contentType.equals("image/png") ||
            contentType.equals("image/gif") ||
            contentType.equals("image/webp"));
}

Error handling

The upload process includes basic error handling:
try {
    // Upload logic
    return ResponseEntity.ok("File uploaded successfully");
}
catch (Exception e) {
    return ResponseEntity.status(500).body("Uploaded failed: " + e.getMessage());
}

Common error scenarios

If the provided event ID doesn’t exist, a RuntimeException is thrown:
Event event = eventRepository.findById(request.getEventId())
    .orElseThrow(() -> new RuntimeException("Event not found"));
If the S3 upload fails (network issues, permissions, etc.), the exception is caught and a 500 error is returned with the error message.
If transferring the multipart file to a temporary file fails, an IOException is caught and returned in the error response.

Upload features

Multipart upload

Standard multipart form data support allows uploading from any HTTP client, including web browsers and mobile apps.

Cloud storage

Images are stored in AWS S3, providing unlimited scalability and high availability.

Metadata tracking

Each upload creates a database record linking the image to its event with visibility controls.

Original filenames

Original filenames are preserved as S3 keys, making it easy to identify and organize photos.

Best practices

File validation: Always validate file types, sizes, and content on the backend before uploading to S3 to prevent malicious uploads.
Unique filenames: Consider generating unique filenames (using UUIDs) instead of using original filenames to prevent naming conflicts when multiple users upload files with the same name.
Progress feedback: Implement upload progress indicators in your frontend to improve user experience, especially for large files.
Cleanup: Ensure temporary files are deleted after successful or failed uploads to prevent disk space issues.
Security: Implement proper authentication and authorization to ensure users can only upload to events they have access to. Validate event passwords for guest uploads.

Example frontend implementation

Here’s an example of how to upload an image from a web frontend:
async function uploadImage(file, eventId) {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('eventId', eventId);

  try {
    const response = await fetch('/api/image', {
      method: 'POST',
      body: formData,
      headers: {
        'Authorization': `Bearer ${accessToken}`
      }
    });

    if (response.ok) {
      console.log('Upload successful!');
    } else {
      console.error('Upload failed:', await response.text());
    }
  } catch (error) {
    console.error('Upload error:', error);
  }
}

Build docs developers (and LLMs) love