DEV Community

Cover image for How To Integrate Auto-Save For PDFs In React With Amazon S3
Phinter Atieno for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

How To Integrate Auto-Save For PDFs In React With Amazon S3

TL;DR: Developers often struggle with managing and storing PDF documents in cloud environments. This guide shows how to use Syncfusion’s React PDF Viewer and File Manager to auto-save and retrieve PDFs from Amazon S3, ensuring secure and seamless document handling.

Managing PDF documents in cloud environments can be tricky, especially when you need real-time saving, secure access, and seamless integration with your frontend. A reliable cloud storage solution like Amazon S3 significantly enhances document access, sharing, security, and performance in React applications.

However, integrating cloud storage can be complex, requiring careful configuration to ensure both efficiency and security. In this blog, we’ll explore how to achieve seamless PDF management using Amazon S3 (AWS). We’ll use Syncfusion’s React PDF Viewer and Amazon S3 to build a robust document management system that auto-saves user changes and keeps everything in sync.

With the Syncfusion® React PDF Viewer, users can effortlessly load, interact, and auto-save PDF documents while securely storing and retrieving files from Amazon S3.

We’ll walk through key functionalities such as:

  • Remote PDF loading
  • Secure file access
  • Customizable viewer options

By the end of this guide, you’ll be equipped to elevate your document handling experience in React using Syncfusion and AWS.

Setting Up Amazon S3

Amazon S3 (Simple Storage Service) is a scalable, secure, and highly available cloud storage solution from AWS, ideal for web applications, backups, and content delivery.

Setting up Amazon S3 involves three key steps:

Step 1: Create an S3 Bucket

Sign in to the AWS Management Console and navigate to Amazon S3 via the AWS services menu. Click Create bucket, then provide a unique name, select a region, and configure the public access settings based on your requirements. Finally, click Create bucket to complete the process.

Step 2: Configure Bucket Permissions

Open your newly created S3 bucket and go to the Permissions tab. Configure the Bucket Policy to control read/write access and adjust CORS settings if necessary.

Step 3: Retrieve Access Credentials

Navigate to the IAM service and create a new IAM user with Programmatic access. Attach the AmazonS3FullAccess policy or customize permissions as needed. Save the Access Key ID and Secret Access Key for authentication in your application.

Setting up the ASP.NET Core backend (Server-Side)

Step 1: Create a new ASP.NET Core Web API project

Create an ASP.NET Core Web API project using Visual Studio or the .NET CLI. Next, install the AWSSDK.S3 NuGet package to enable interaction with Amazon S3, which allows for file uploads, downloads, and storage management.

Step 2: Configure Amazon S3

Add the Amazon S3 credentials and bucket details to the appsettings.json file. Include the Access Key, Secret Key, Bucket Name, and Region as shown below:

appsettings.json

{
    "Logging": {
        "LogLevel": {
            "Default": "Warning"
        }
    },
    "AllowedHosts": "*",
    "AccessKey": " <YOUR ACCESS KEY> ",
    "SecretKey": " <YOUR SECRET KEY> ",
    "BucketName": " <YOUR BUCKET NAME> ",
    "AWS": {
        "Region": " <YOUR REGION> "
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement the Amazon S3 storage service

Create a service class, AmazonS3DocumentStorageService.cs, to handle file operations. This service includes two key methods:

  • FetchDocumentAsync: Retrieves a PDF document from Amazon S3 storage.
  • UploadDocumentAsync: Saves and uploads a document file to Amazon S3 storage.
public class AmazonS3DocumentStorageService: IAmazonS3DocumentStorageService
{
    private readonly string _accessKey;
    private readonly string _secretKey;
    private readonly string _bucketName;
    private readonly RegionEndpoint _region;
    private readonly IAmazonS3 _s3Client;
    private readonly ILogger _logger;
    private readonly AmazonS3DocumentManager _fileProvider;
    private readonly string _rootFolderName;
    public string basePath;
    public AmazonS3DocumentStorageService(
        IAmazonS3 s3Client,
        IWebHostEnvironment hostingEnvironment,
        IConfiguration configuration,
        ILogger logger)
    {
        _s3Client = s3Client;
        _logger = logger;
        _accessKey = configuration["AccessKey"];
        _secretKey = configuration["SecretKey"];
        _bucketName = configuration["BucketName"];
        _region = RegionEndpoint.GetBySystemName(configuration["AWS:Region"]);
        // Folder name created inside the container
        _rootFolderName = "Files";
        // Initialize basePath from the hosting environment and clean it up
        basePath = hostingEnvironment.ContentRootPath;
        basePath = basePath.Replace("../", "");
        _fileProvider = new AmazonS3DocumentManager();
        _fileProvider.RegisterAmazonS3(_bucketName, _accessKey, _secretKey, _region.SystemName);
    }
    /// <summary>
    /// Loads a document from Amazon S3 and returns the serialized document.
    /// </summary>
    /// <param name="documentName">The name of the document to load.</param>
    /// <returns>An IActionResult containing the serialized document if successful, or an error status code.</returns>
    public async Task<IActionResult> FetchDocumentAsync(string documentName)
    {
        try
        {
            // Create a new S3 client for this operation.
            using var s3Client = new AmazonS3Client(_accessKey, _secretKey, _region);
            using var stream = new MemoryStream();
            // Get the document object from the S3 bucket.
            var response = await s3Client.GetObjectAsync(_bucketName, $"{_rootFolderName}/{documentName}");
            await response.ResponseStream.CopyToAsync(stream);
            stream.Seek(0, SeekOrigin.Begin);                              
            // With the following corrected line:
            return new FileContentResult(stream.ToArray(), "application/pdf")
            {
                FileDownloadName = documentName
            };
        }
        catch (AmazonS3Exception ex)
        {
            _logger.LogError(ex, "S3 error loading document");
            return new StatusCodeResult(500);
        }
    }
    /// <summary>
    /// Saves a document to Amazon S3 using the provided form file.
    /// </summary>
    /// <param name="file">The uploaded form file to be saved.</param>
    /// <param name="documentName">The name under which the document will be stored in S3.</param>
    /// <returns>An IActionResult indicating whether the save operation was successful.</returns>
    public async Task<IActionResult> UploadDocumentAsync(IFormFile file, string documentName)
    {
        try
        {
            // Create a new S3 client instance to upload the document.
            using var s3Client = new AmazonS3Client(_accessKey, _secretKey, _region);
            using var stream = new MemoryStream();
            // Copy the uploaded file content to a memory stream.
            await file.CopyToAsync(stream);
            stream.Seek(0, SeekOrigin.Begin);
            // Construct the put object request with the necessary details.
            var request = new PutObjectRequest
            {
                BucketName = _bucketName,
                Key = $"{_rootFolderName}/{documentName}",
                InputStream = stream
            };
            // Upload the file to the S3 bucket.
            await s3Client.PutObjectAsync(request);
            return new OkResult();
        }
        catch (AmazonS3Exception ex)
        {
            _logger.LogError(ex, "S3 error saving document");
            return new StatusCodeResult(500); }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create API endpoints in a controller

Create an API controller to handle file operations. This controller includes two key methods that internally invoke the corresponding service methods:

  • FetchDocument: Invokes the service method to retrieve a PDF document from Amazon S3 storage.
  • UploadDocument: Invokes the service method to save and upload a document file to Amazon S3 Storage.

The code example below demonstrates the key controller methods for handling file operations in Controllers/AmazonS3DocumentStorageController.cs

/// <summary>
/// Controller for handling AWS file operations and document management
/// </summary>
[Route("api/[controller]")]
[EnableCors("AllowAllOrigins")]
public class AmazonS3DocumentStorageController : ControllerBase
{
    private readonly IAmazonS3DocumentStorageService _documentStorageService;
    /// <summary>
    /// Constructor injecting the file provider service dependency.
    /// </summary>
    /// <param name="documentStorageService">Service for performing file operations</param>
    public AmazonS3DocumentStorageController(IAmazonS3DocumentStorageService documentStorageService)
    {
        _documentStorageService = documentStorageService;
    }
    /// <summary>
    /// Retrieves a document from Amazon S3 storage in JSON format.
    /// </summary>
    /// <param name="args">File operation parameters including path and action type</param>
    /// <returns>Result of the file operation</returns>
    [HttpPost("FetchDocument")]
    public async Task FetchDocument([FromBody] Dictionary<string, string=""> jsonObject)
    {
        if (!jsonObject.TryGetValue("documentName", out var docName))
            return BadRequest("Document name required");
        return await _documentStorageService.FetchDocumentAsync(docName);
    }
    /// <summary>
    /// Saves uploaded document to Amazon S3 storage.
    /// </summary>
    /// <param name="data">Form data containing file and document name</param>
    /// <returns>Action result indicating success or failure.</returns>
    [HttpPost("UploadDocument")]
    public async Task UploadDocument([FromForm] IFormCollection data)
    {
        if (data.Files.Count == 0)
            return BadRequest("No file provided");
        var documentName = data.TryGetValue("documentName", out var values) && values.Count > 0 ? values[0] : string.Empty;
        return await _documentStorageService.UploadDocumentAsync(data.Files[0], documentName);
    }
}
Enter fullscreen mode Exit fullscreen mode

Setting up React Frontend Client-Side

Step 1: Create a React App and add dependencies

Create a React app and integrate the Syncfusion® components: PDF Viewer, and File Manager, to interact with Amazon S3 storage. This integration enables file uploads, downloads, and storage management within your application.

  • PDF Viewer: Loads, displays, allows interaction with, and auto-saves PDF documents directly from cloud storage.
  • File Manager: Interactively browses and manages files stored in Amazon S3 Blob Storage.

Step 2: Implement File Manager operations

Create a new TypeScript JSX file named AmazonS3FileManager.tsx to implement the File Manager, allowing users to display, browse, and manage files stored in Amazon S3. The example code below demonstrates this implementation:

import * as React from 'react'; 
import {
    FileManagerComponent,
    Inject,
    NavigationPane,
    DetailsView,
    Toolbar
} from '@syncfusion/ej2-react-filemanager';
import { DialogComponent } from '@syncfusion/ej2-react-popups';
interface AmazonS3Props {
    // Callback function triggered when a file is selected in the file manager
    onFileSelect: (filePath: string, fileType: string, fileName: string) => void;
}

const AmazonS3FileManager: React.FC<AmazonS3Props> = ({ onFileSelect }) => {
    // Base URL for backend API handling Azure file operations
    const hostUrl: string = "http://localhost:62869/";
    // State management for file manager dialog visibility
    const [showFileManager, setShowFileManager] = React.useState(true);
    // Reference to access the FileManager component methods
    let fileManagerRef = React.useRef<FileManagerComponent>(null);
    // Shows the file manager when the open button is clicked and clears the previous selection item
    const handleOpenButtonClick = () => {
        // Clear the previous selection
        if (fileManagerRef.current) {
            fileManagerRef.current.clearSelection();
            setTimeout(() => {
                fileManagerRef.current.refreshFiles();
            }, 100);
        }
        setShowFileManager(true);
    };
    // Handles file open event from file manager
    const handleFileOpen = (args: any) => {
        if (args.fileDetails.isFile) {
            const selectedPath = args.fileDetails.path || args.fileDetails.filterPath + args.fileDetails.name;
            const fileType = args.fileDetails.type;
            const fileName = args.fileDetails.name;
            onFileSelect(selectedPath, fileType, fileName); // Pass the file path and file type to load in the Document Editor
            setShowFileManager(false); // Close the File Manager Dialog
        }
    };
    return (
        <div>
            <button id="openAmazonS3BucketStorage" onClick={handleOpenButtonClick}>
                Open the AWS file manager
            </button>
            {/* File Manager Dialog */}
            <DialogComponent
                id="dialog-component-sample"
                header="File Manager"
                visible={showFileManager}
                width="80%"
                height="80%"
                showCloseIcon={true}
                closeOnEscape={true}
                target="body"
                beforeClose={() => setShowFileManager(false)}
                onClose={() => setShowFileManager(false)} // Close the dialog when closed
            >
                <FileManagerComponent
                    id="aws-file"
                    ref={fileManagerRef}
                    ajaxSettings={{
                        url: hostUrl + 'api/AmazonS3DocumentStorage/ManageDocument',
                        downloadUrl: hostUrl + 'api/AmazonS3DocumentStorage/DownloadDocument',
                    }}
                    toolbarSettings={{
                        items: ['SortBy', 'Copy', 'Paste', 'Delete', 'Refresh', 'Download', 'Selection', 'View', 'Details']
                    }}
                    contextMenuSettings={{
                        file: ['Open', 'Copy', '|', 'Delete', 'Download', '|', 'Details'],
                        layout: ['SortBy', 'View', 'Refresh', '|', 'Paste', '|', '|', 'Details', '|', 'SelectAll'],
                        visible: true
                    }}
                    fileOpen={handleFileOpen} // Attach the fileOpen event
                >
                    <Inject services={[NavigationPane, DetailsView, Toolbar]} />
                </FileManagerComponent>
            </DialogComponent>
        </div>
    );
};

export default AmazonS3FileManager;
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement PDF Viewer operations

Create a new TypeScript JSX file named PdfViewer.tsx to implement the PDF Viewer functionalities, which include opening, interacting with, saving, and downloading PDF documents.

a. Custom toolbar options: Import the necessary dependencies and add the following custom toolbar items:

  • Open – Displays the File Manager to load a selected PDF document from Amazon S3.
  • Download – Downloads the currently loaded PDF document locally.
import React, { useRef, useState } from 'react';
import {
    PdfViewerComponent, Toolbar, Magnification, Navigation, LinkAnnotation, BookmarkView,
    ThumbnailView, Print, TextSelection, Annotation, TextSearch, FormFields, FormDesigner, Inject
} from '@syncfusion/ej2-react-pdfviewer';
import AwsFileManager from './AWSFileManager.tsx';
function PdfViewer() {
    // Reference to access PDF viewer methods
    const containerRef = useRef<PdfViewerComponent>(null);
    // State to track the current document name
    const [fileName, setFileName] = useState<string>('None');
    // API service base URL
    const hostUrl: string = "http://localhost:62871/";

    // Custom toolbar button for opening AWS file manager
    const OpenOption = {
        prefixIcon: 'e-icons e-folder',
        id: 'OpenFileManager',
        text: 'Save',
        tooltipText: 'Open file manager',
        align: 'Left'
    };    

   // Handle toolbar button click events
    const toolbarClick = (args: any): void => {
        // Get a reference to the file manager open button
        const openButton = document.getElementById('openAwsBucketStorage');
        // Always check if containerRef.current exists before using it
        if (!containerRef.current) return;
        if (args.item !== undefined) {
            switch (args.item.id) {
                case 'OpenFileManager':
                    if (openButton) {
                        openButton.click(); // Trigger the file manager open button click
                    }
                    break;
                default:
                    break;
            }
        }
    };
   return (
        <div>
            {/* AWS File Manager component for browsing and selecting files */}
            <div>
                <AwsFileManager onFileSelect={loadFile} />
            </div>
            {/* PDF Viewer container */}
            <div id="pdf-viewer-div" style={{ display: "block" }}>
                <div id="pdf-header">
                    {fileName || 'None'}
                </div>
                {/* Syncfusion PDF Viewer component with various tools and capabilities */}
                <PdfViewerComponent
                    id="container"
                    ref={containerRef}
                    resourceUrl="https://cdn.syncfusion.com/ej2/26.2.11/dist/ej2-pdfviewer-lib"
                    style={{ height: '700px' }}
                    toolbarSettings={{
                        showTooltip: true,
                        toolbarItems: [OpenOption,'PageNavigationTool','MagnificationTool','PanTool','SelectionTool','SearchOption', 'PrintOption','UndoRedoTool','AnnotationEditTool','FormDesignerEditTool','CommentTool', 'SubmitForm','DownloadOption']
                    }}
                    toolbarClick={toolbarClick}
                    annotationAdd={autoSave}
                    formFieldAdd={autoSave}
                    addSignature={autoSave}
                    annotationPropertiesChange={autoSave}
                    formFieldPropertiesChange={autoSave}
                    commentStatusChanged={autoSave}
                    downloadFileName={fileName}
                    exportAnnotationFileName={fileName}
                >
                    {/* Inject required PDF viewer services and features */}
                    <Inject services={[
                        Toolbar,
                        Magnification,
                        Navigation,
                        Annotation,
                        LinkAnnotation,
                        BookmarkView,
                        ThumbnailView,
                        Print,
                        TextSelection,
                        TextSearch,
                        FormFields,
                        FormDesigner
                    ]} />
                </PdfViewerComponent>
            </div>
        </div>
    );}
export default PdfViewer;
Enter fullscreen mode Exit fullscreen mode

b . Open a PDF from the File Manager (Amazon S3 Storage)

 const loadFile = (filePath: string, fileType: string, fileName: string): void => {
        if (!containerRef.current) {
            console.error('PDF Viewer is not loaded yet.');
            return;
        }
        // Update state with the current document name
        setFileName(fileName);
        if (fileType === '.pdf') {
            // Fetch the PDF document from AWS S3 through our API
            fetch(hostUrl + 'api/AmazonS3DocumentStorage/FetchDocument', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json;charset=UTF-8' },
                body: JSON.stringify({ documentName: fileName })
            })
                .then(response => {
                    if (response.status === 200 || response.status === 304) {
                        return response.blob(); // For PDFs, it's better to handle as blob
                    } else {
                        throw new Error('Error loading PDF document');
                    }
                })
                .then(blob => {
                    const pdfViewerDiv = document.getElementById("container");
                    if (pdfViewerDiv) {
                        pdfViewerDiv.style.display = "block";
                    }
                    // Create object URL from blob and load it in the PDF viewer
                    const objectUrl = URL.createObjectURL(blob);
                    containerRef.current?.load(objectUrl, null);
                })
                .catch(error => {
                    console.error('Error loading PDF document:', error);
                });
        } else {
            alert('The selected file type is not supported. Please select a PDF file.');
        }
    };
Enter fullscreen mode Exit fullscreen mode

c. Automatically save the document to Amazon S3 storage: Automatically saves the PDF document to Amazon S3 storage as users add annotations and fill form fields, ensuring all changes are securely preserved in real time.

// Auto-save function that sends the modified PDF to AWS storage
    const autoSave = async (): Promise<void> => {
        if (!containerRef.current) return;
        try {
            // Get the document as a binary blob
            const blob: Blob = await containerRef.current.saveAsBlob();
            let exportedDocument = blob;
            let formData: FormData = new FormData();
            formData.append('documentName', fileName);
            formData.append('data', exportedDocument);
            let req = new XMLHttpRequest();
            // Send document to backend API for AWS storage
            req.open(
                'POST',
                hostUrl + 'api/AmazonS3DocumentStorage/UploadDocument',
                true
            );
            req.onreadystatechange = () => {
                if (req.readyState === 4 && (req.status === 200 || req.status === 304)) {
                    // Auto save completed
                    // Success handler can be added here if needed
                }
            };
            req.send(formData);
        }
        catch (error) {
            console.error('Error saving document:', error);
        }
    };
Enter fullscreen mode Exit fullscreen mode

d. Download a copy of the document to local storage: When you click the Download button in the toolbar, a copy of the document currently open in the editor will be downloaded or saved to the local storage.

To run the project, launch the server-side ASP.NET Core API and start the client-side React application. The results of the file operations performed on the client side will be displayed as shown in the image below.

Open Edit and Auto-Save to-Amazon S3 in React

GitHub reference

For the complete project, refer to the GitHub demo.

Conclusion

Thanks for reading! In this blog, we have explored how to efficiently open, interact, and auto-save PDF documents in Amazon S3 (AWS) Storage using Syncfusion’s PDF Viewer and File Manager within a React application. By following the steps outlined, you can implement a secure document management system that enables users to work with PDFs directly in the cloud, without compromising performance or usability.

Integrating Syncfusion’s PDF Viewer and File Manager with Amazon S3 empowers developers to deliver a scalable, secure, and interactive document experience within React applications. Whether you’re building enterprise-grade tools or internal dashboards, this setup ensures your PDFs are always accessible, editable, and up to date.

And while this guide focuses on Amazon S3 PDF Storage, Syncfusion’s PDF Viewer is flexible enough to work with other cloud platforms like Azure Blob Storage or Google Cloud Storage, offering a reliable and customizable solution for modern document workflows.

The new version is available for current customers to download from the license and downloads page. If you are not a Syncfusion® customer, you can try our 30-day free trial for our newest features.

You can also contact us through our support forum, support portal, or feedback portal. We are always happy to assist you!

Related Blogs

This article was originally published at Syncfusion.com.

Top comments (0)