DEV Community

Cover image for React + PostgreSQL: Build a Document Editor with Auto-Save 
Calvince Moth for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

React + PostgreSQL: Build a Document Editor with Auto-Save 

TL;DR: Developers often struggle with managing Word documents in web apps. This guide walks through building a React-based document editor with auto-save functionality, backed by PostgreSQL and an ASP.NET Core API.

Working with Word documents in web applications can be complex, especially when it comes to editing, saving, and storing them securely. But with React, PostgreSQL, and ASP.NET Core Web API, the process becomes smoother and more intuitive.

In this blog, we’ll show you how to build a robust document management system using these technologies, enhanced by powerful Syncfusion components like the React DOCX Editor and File Manager components.

This solution enables users to open, edit, and auto-save Word documents directly in the browser, without the need for any external software. Key features include:

  • Auto-save functionality to ensure document changes are preserved automatically
  • Structured file management supported by a normalized PostgreSQL schema
  • In-browser editing with Syncfusion React DOCX Editor, offering a rich, Word-like user experience
  • Interactive file browsing using Syncfusion File Manager, along with RESTful APIs for operations such as upload, download, delete, copy, and search

Setting up ASP.NET Core backend (Server-side)

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

Create a new ASP.NET Core Web API project using Visual Studio or the .NET CLI. Once the project is set up, install the following NuGet packages to enable seamless integration with a PostgreSQL database and support for file uploads, downloads, and document management.

Step 2: Configuring PostgreSQL in ASP.NET Core

Let’s assume you already have a PostgreSQL database set up with the necessary table structure to store files, folders, and their associated metadata. If you’re new to PostgreSQL, we recommend checking out the official documentation to install, create, and manage a new database and table before proceeding.

In our example project, we use a single normalized table named documents. This table supports a hierarchical structure like a traditional file system, allowing it to represent files and folders efficiently.

Add a connection string

The connection string defines how your .NET application connects to the PostgreSQL database. It includes key details such as the host, port, database name, and login credentials. This enables Entity Framework Core to interact with the database for reading and writing data.

appsettings.json

{
    "ConnectionStrings": {
        "DefaultConnection": "Host=localhost;Port=5432;Database=<DATABASE NAME>;Username=<USERNAME>;Password <YOUR PASSWORD>"
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: Be sure to replace the database name, username, and password with your actual database credentials.

Define the DbContext and document model

The DbContext class represents a session with the database and is responsible for querying and saving data using Entity Framework Core. It maps your Document model to the Documents table in PostgreSQL, enabling LINQ-based queries and full CRUD operations. This class acts as the bridge between your C# code and the underlying database.

DocumentContext.cs

using Microsoft.EntityFrameworkCore;
public class DocumentContext : DbContext
{
    public DocumentContext(DbContextOptions<DocumentContext> options) : base(options) 
    {
    }
    public DbSet<Document> Documents { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The Document model defines the structure of the Documents table in PostgreSQL. It includes properties like Id, Name, FileData, CreatedAt, and ModifiedAt, which map directly to the corresponding table columns. Data annotations help configure how the class interacts with the database to ensure proper behavior during operations.

Document.cs

public class Document
{
    /// <summary>
    /// Gets or sets the unique identifier for the document
    /// This is the primary key and is auto-incremented.
    /// </summary>
    [Key]
    [Column("id")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    /// <summary>
    /// Gets or sets the name of the document.
    /// </summary>
    [Column("name")]
    public string Name { get; set; }
    /// <summary>
    /// Gets or sets the binary file data of the document.
    /// </summary>
    [Column("file_data")]
    public byte[] FileData { get; set; }
    /// <summary>
    /// Gets or sets the timestamp when the document was last modified.
    /// </summary>
    [Column("modified_at")]
    public DateTime ModifiedAt { get; set; }

    /// <summary>
    /// Gets or sets the timestamp when the document was created.
    /// </summary>
    [Column("created_at")]
    public DateTime CreatedAt { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Register the DbContext in Program.cs

Registering the DbContext in Program.cs is essential for configuring dependency injection (DI) in your ASP.NET Core application. This allows the framework to inject the DocumentContext wherever it’s needed, enabling your app to interact with the PostgreSQL database using Entity Framework Core.

The AddDbContext method sets up the DocumentContext to use the PostgreSQL connection string named DefaultConnection in appsettings.json, allowing database operations such as querying, inserting, updating, and deleting records.

Add the following code to your Program.cs file:

// Add database context
builder.Services.AddDbContext<DocumentContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement document storage service (File manager)

We need to create a service class to handle file-related operations. This service is responsible for interacting with the PostgreSQL database and includes key methods for managing Word documents:

  • GetDocumentsAsync: Retrieves all Word documents stored in the PostgreSQL database.
  • GetDocumentDetailsAsync: Retrieves detailed information for a specific Word document based on its ID.

Note: This service class may also include additional methods to support file management operations used by the Syncfusion File Manager.

Add the following code to your service class:

public class PostgresDocumentStorageService
{
    private readonly DocumentContext _documentContext;

    public PostgresDocumentStorageService(DocumentContext context)
    {
        _documentContext = context;
    }

    /// <summary>
    /// Retrieves all files in the database formatted for a file manager UI.
    /// </summary>
    public async Task<object> GetDocumentsAsync()
    {
        var documents = await _documentContext.Documents
            .AsNoTracking()
            .Select(d => new
            {
                name = d.Name,
                size = d.FileData != null ? d.FileData.Length : 0,
                dateModified = d.ModifiedAt ,
                dateCreated = d.CreatedAt,
                hasChild = false,
                isFile = true,
                type = Path.GetExtension(d.Name),
                filterPath = "\\",
                id = d.Id.ToString()
            })
            .ToListAsync();

        var docCount = await _documentContext.Documents.MaxAsync(d => d.Id);
        var response = new
        {
            cwd = new
            {
                name = "Documents",
                size = 0,
                dateModified = DateTime.UtcNow,
                dateCreated = DateTime.UtcNow,
                hasChild = true,
                isFile = false,
                type = "",
                filterPath = ""
            },
            files = documents,
            docCount = docCount
        };

        return response;
    }
    /// <summary>
    /// Retrieves file details for the specified document.
    /// </summary>
    public async Task<object> GetDocumentDetailsAsync(string path, string[] names, FileManagerDirectoryContent[] data)
    {
        if (data?.Length == 0)
        {
            return BuildErrorResponse("No items provided for details.");
        }
        if (data.Length > 1)
        {
            return new
            {
                cwd = (object)null,
                files = (object)null,
                error = (object)null,
                details = new
                {
                    name = "Multiple Files",
                    location = path ?? "\\",
                    isFile = false,
                    size = "",
                    created = "",
                    modified = "",
                    multipleFiles = true
                }
            };
        }

        var item = data[0];
        var doc = await _documentContext.Documents
            .Where(d => d.Id.ToString() == item.Id)
            .Select(d => new
            {
                d.Name,
                d.CreatedAt,
                d.ModifiedAt,
                Size = d.FileData != null ? d.FileData.Length : 0,
                Location = item.FilterPath ?? "\\"
            })
            .FirstOrDefaultAsync();

        if (doc == null)
        {
            return BuildErrorResponse("Item not found.");
        }

        return new
        {
            cwd = (object)null,
            files = (object)null,
            error = (object)null,
            details = new
            {
                name = doc.Name,
                location = doc.Location,
                isFile = true,
                size = FormatFileSize(doc.Size),
                created = doc.CreatedAt.ToString("M/d/yyyy h:mm:ss tt"),
                modified = doc.ModifiedAt.ToString("M/d/yyyy h:mm:ss tt"),
                multipleFiles = false
            }
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create API endpoints in a Controller

We need to create an API controller to handle file operations. This controller exposes key HTTP endpoints and internally calls the corresponding methods from the service class.

Key methods:

  • GetDocumentAsync: Invokes the service method to retrieve a Word document from the PostgreSQL database.
  • SaveDocumentAsync: Invokes the service method to save and upload a Word document to the PostgreSQL database.

Add the following code to your API controller file

[Route("api/[controller]")]
[ApiController]
public class PostgresDocumentStorageController : ControllerBase 
{
    private readonly PostgresDocumentStorageService _documentStorageService; 
    private readonly DocumentContext _documentContext;
    public PostgresDocumentStorageController(DocumentContext context)
    {
        _documentContext = context;
        _documentStorageService = new PostgresDocumentStorageService(_documentContext);
    }
    /// <summary>
    /// Handles File Manager operations like read, delete, details, search, and copy
    /// </summary>
    [HttpPost]
    public async Task<IActionResult> HandleFileManagerOperationsAsync([FromBody] FileManagerDirectoryContent args)
    {
        if (args == null || string.IsNullOrEmpty(args.Action))
        {
            return BadRequest("Invalid request.");
        }

        return args.Action.ToLower() switch
        {
            "read" => Ok(await _documentStorageService.GetDocumentsAsync()),
            "delete" => Ok(await _documentStorageService.DeleteAsync(args.Path, args.Names, args.Data)),
            "details" => Ok(await _documentStorageService.GetDocumentDetailsAsync(args.Path, args.Names, args.Data)),
            "search" => Ok(await _documentStorageService.SearchAsync(args.Path, args.SearchString, args.ShowHiddenItems, args.CaseSensitive, args.Data)), // You need to implement SearchAsync
            "copy" => Ok(await _documentStorageService.CopyAsync(args.Path, args.TargetPath, args.Names, args.Data, args.RenameFiles)), // You need to implement CopyAsync
            _ => BadRequest($"Unknown action: {args.Action}")
            };
        }

        /// <summary>
        /// Returns document data as serialized SFDT JSON for Syncfusion DocumentEditor.
        /// </summary>

        [HttpGet("{docId}/getDocumentAsync")]
        public async Task<IActionResult> GetDocumentAsync(string docId)
        {
            int id = int.Parse(docId);
            var document = await _documentContext.Documents.FirstOrDefaultAsync(d => d.Id == id);
            if (document == null)
        {
            return NotFound();
        }

        try
        {
            using var memoryStream = new MemoryStream(document.FileData);
            WordDocument wordDocument = WordDocument.Load(memoryStream, FormatType.Docx);
            string sfdtContent = JsonConvert.SerializeObject(wordDocument);
            wordDocument.Dispose();

            return Content(sfdtContent, "application/json");
    }
    catch (Exception ex)
    {
        return BadRequest($"Error processing document: {ex.Message}");
    }
}

/// <summary>
/// Saves or updates a document in the database using Base64-encoded content.
/// </summary>

[HttpPost("{id}/saveDocumentAsync")]
public async Task<IActionResult> SaveDocumentAsync(int id, [FromBody] SaveDocument fileData)
{
    var document = await _documentContext.Documents.FindAsync(id);

    try
    {
        // Convert the Base64Content-encoded document to a byte array
        byte[] data = Convert.FromBase64String(fileData.Base64Content);

        var existingDoc = await _documentContext.Documents
            .FirstOrDefaultAsync(d => d.Name == fileData.FileName);

        if (existingDoc != null)
        {
            document.FileData = data;
            document.ModifiedAt = DateTime.UtcNow;
            await _documentContext.SaveChangesAsync();
        }
        else
        {
            var newDoc = new Document
            {
                Id = id,
                Name = fileData.FileName,
                FileData = data,
                ModifiedAt = DateTime.UtcNow,
                CreatedAt = DateTime.UtcNow,
            };
            _documentContext.Documents.Add(newDoc);
            await _documentContext.SaveChangesAsync();
        }
        return Ok("Document saved.");
    }
    catch (Exception ex)
    {
        return BadRequest($"Save failed: {ex.Message}");
    }
}

Enter fullscreen mode Exit fullscreen mode

Setting up React frontend (Client-side)

Step 1: Create a React app and add dependencies

We create a React app and integrate the Syncfusion components, React DOCX Editor, and File Manager, to interact with a PostgreSQL database. This integration enables file uploads, downloads, and storage management within your application.

Step 2: Implement File Manager operations

We create a new JavaScript file to implement a File Manager that allows users to browse and manage the files stored in a PostgreSQL database.

FileManager.js:

import { 
    FileManagerComponent, 
    Inject, 
    NavigationPane, 
    DetailsView, 
    Toolbar as FileManagerToolbar
} from '@syncfusion/ej2-react-filemanager'; 
import { DialogComponent } from '@syncfusion/ej2-react-popups';
// FileManager component props:
const FileManager = ({ 
    onFileSelect, 
    onFileManagerLoaded, 
    editorRef, 
    fileManagerRef, 
    visible, 
    setVisible 
}) => {
    // Called after FileManager successfully loads
    const onSuccess = (args) => {
        const maxId = args.result.docCount; // Retrieve document count
        if (onFileManagerLoaded) onFileManagerLoaded(maxId); // Call parent callback
    };

    // Base API URL
    const hostUrl = 'https://localhost:44305/';

    // Loads a document by its ID into the DocumentEditor
    const loadDocument = async (docId) => {
        try {
            const response = await fetch(hostUrl + `api/ PostgresDocumentStorage /${docId}/getDocumentAsync`);
            const data = await response.text();
            if (editorRef?.current?.documentEditor) {
                editorRef.current.documentEditor.open(data); // Load content into the editor
            }
        } catch (err) {
            console.error('Error loading document', err);
        }
    };

    // Triggered when a file is opened via double-click or context menu
    const handleFileOpen = (args) => {
        if (args.fileDetails.isFile) {
            const fileId = args.fileDetails.id;
            const fileName = args.fileDetails.name;
            if (typeof onFileSelect === 'function') {
                onFileSelect(fileId, fileName); // Call parent callback with file info
            }
            loadDocument(fileId); // Load the selected document
            setVisible(false); // Close the dialog
        }
    };

    return (
        <DialogComponent
            id="dialog-component-sample"
            header="File Manager"
            visible={visible}
            width="95%"
            height="85%"
            showCloseIcon={true}
            closeOnEscape={true}
            target="body"
            beforeClose={() => setVisible(false)} // Set dialog visibility to false before closing
            onClose={() => setVisible(false)} // Ensure dialog state is updated on close
        >
            <FileManagerComponent
                id="file-manager"
                ref={fileManagerRef}
                ajaxSettings={{
                    url: hostUrl + 'api/ PostgresDocumentStorage, // API for file management
                    downloadUrl: hostUrl + 'api/ PostgresDocumentStorage /downloadAsync', // API for file download
                }}
                toolbarSettings={{
                    items: ['SortBy', 'Copy', 'Paste', 'Delete', 'Refresh', 'Download', 'Selection', 'View', 'Details'],
                }}
                contextMenuSettings={{
                    file: ['Open', 'Copy', '|', 'Delete', 'Download', '|', 'Details'], // Context menu options for files
                    layout: ['SortBy', 'View', 'Refresh', '|', 'Paste', '|', '|', 'Details', '|', 'SelectAll'], // Layout menu options
                    visible: true,
                }}
                fileOpen={handleFileOpen} // Callback for opening a file
                success={onSuccess} // Callback for successful data load
            >
                {/* Inject necessary services for FileManager */}
                <Inject services={[NavigationPane, DetailsView, FileManagerToolbar]} />
            </FileManagerComponent>
        </DialogComponent>
    );

};
export default FileManager;
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement DOCX Editor operations

We create another JavaScript file to implement the Word Document Editor functionalities, including creating, opening, editing, and saving documents.

  1. Custom toolbar options: Import the necessary dependencies and add the following custom toolbar items:
    • New: Creates a new document.
    • Open: Displays the File Manager to load a selected document.
    • Download: Downloads the current document locally.

DocumentEditor.js file:

import React, { useState, useRef } from 'react';
import { DocumentEditorContainerComponent, Toolbar as DocumentEditorToolbar } from '@syncfusion/ej2-react-documenteditor';
import { DialogComponent } from '@syncfusion/ej2-react-popups';
import FileManager from './FileManager';
DocumentEditorContainerComponent.Inject(DocumentEditorToolbar);

const DocumentEditor = () => {
    // State and refs
    const [maxDocId, setMaxDocId] = useState(null); // Track maximum doc ID for new document creation
    const containerRef = useRef(null); // Reference to DocumentEditor container
    const [selectedDocId, setSelectedDocId] = useState(null); // Selected document ID
    const [selectedDocName, setSelectedDocName] = useState(null); // Selected document name
    const [showDialog, setShowDialog] = useState(false); // Controls "New Document" dialog
    const [showFileManager, setShowFileManager] = useState(true); // Controls FileManager dialog visibility
    const fileManagerRef = useRef(null); // Reference to FileManager
    const inputRef = useRef(null); // Input reference for new document name
    const contentChanged = useRef(false); // Flag to track content changes
    const [errorMessage, setErrorMessage] = useState(''); // Error message for name validation
    const randomDefaultName = 'New_Document'; // Default document name

    // Toolbar custom items
    const newToolItem = { 
        prefixIcon: "e-de-ctnr-new", 
        tooltipText: "New", 
        text: "New", 
        id: "CreateNewDoc" 
    };
    const openToolItem = { 
        prefixIcon: "e-de-ctnr-open", 
        tooltipText: "Open file manager", 
        text: "Open", 
        id: "OpenFileManager" 
    };
    const downloadToolItem = { 
        prefixIcon: "e-de-ctnr-download", 
        tooltipText: "Download", 
        text: "Download", 
        id: "DownloadToLocal" 
    };

    const hostUrl = "https://localhost:44305/";

    // Complete toolbar items list
    const toolbarItems = [
        newToolItem, 
        openToolItem, 
        downloadToolItem, 
        'Separator',
        'Undo', 
        'Redo', 
        'Separator', 
        'Image', 
        'Table', 
        'Hyperlink', 
        'Bookmark',
        'TableOfContents', 
        'Separator', 
        'Header', 
        'Footer', 
        'PageSetup', 
        'PageNumber', 
        'Break', 
        'InsertFootnote', 
        'InsertEndnote', 
        'Separator', 
        'Find', 
        'Separator',
        'Comments', 
        'TrackChanges', 
        'Separator', 
        'LocalClipboard', 
        'RestrictEditing',
        'Separator', 
        'FormFields', 
        'UpdateFields', 
        'ContentControl'
    ];

    return ( 
        {/* FileManager dialog for opening files */} <FileManager onFileSelect={loadFileFromFileManager} onFileManagerLoaded={(id) => setMaxDocId(id)} editorRef={containerRef} fileManagerRef={fileManagerRef} visible={showFileManager} setVisible={setShowFileManager} />
        {/* Document name display */}
        <div id="document-header">
            {selectedDocName || (inputRef?.current?.value ? inputRef.current.value + '.docx' : '')}
        </div>

        {/* Main document editor container */}
        <DocumentEditorContainerComponent
            ref={containerRef}
            height="calc(100vh - 65px)"
            serviceUrl="https://ej2services.syncfusion.com/production/web-services/api/documenteditor/"
            enableToolbar={true}
            toolbarItems={toolbarItems}
            toolbarClick={handleToolbarItemClick}
            contentChange={handleContentChange}
        />

        {/* Dialog for creating new documents */}
        <DialogComponent>
            visible={showDialog}
            header='New Document'
            showCloseIcon={true}
            width='400px'
            isModal={true}
            close={() => setShowDialog(false)}
            buttons={[
                {
                    click: handleFileNamePromptOk ,
                    buttonModel: { content: 'Ok', isPrimary: true }
                },
                {
                    click: () => setShowDialog(false),
                    buttonModel: { content: 'Cancel' }
                }
            ]}
            <div className="e-dialog-content">
                <p>Enter document name</p>
                <input
                    ref={inputRef}
                    type="text"
                    className="e-input"
                    placeholder="Document name"
                    defaultValue={randomDefaultName}
                    style={{ width: '100%', marginTop: '10px' }}
                />
                {errorMessage && (
                    <div style={{ color: 'red', marginTop: '4px' }}>
                        {errorMessage}
                    </div>
                )}
            </div>
        </DialogComponent>
    ); 
}; 
export default DocumentEditor;
Enter fullscreen mode Exit fullscreen mode
  1. Open a document from the File Manager (PostgreSQL database):
// Callback function to load file selected in the file manager
const loadFileFromFileManager = (fileId, fileName) => {
    setSelectedDocId(fileId);
    containerRef.current.documentEditor.documentName = fileName;
    setSelectedDocName(fileName);
};
Enter fullscreen mode Exit fullscreen mode
  1. Automatically save the document to the PostgreSQL database: The edited document will be automatically saved every 1000 milliseconds.
// Save document to the server as base64
const autoSaveDocument = () => {
    const editor = containerRef.current?.documentEditor;
    editor.saveAsBlob('Docx').then((blob) => {
        const reader = new FileReader();
        reader.onloadend = async () => {
            const base64Data = reader.result?.toString().split(',')[1];
            const fileName = selectedDocName || (inputRef.current.value || 'Untitled') + '.docx';
            containerRef.current.documentEditor.documentName = fileName;

            try {
                await fetch(hostUrl + `api/ PostgresDocumentStorage /${selectedDocId}/saveDocumentAsync`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ Base64Content: base64Data, FileName: fileName })
                });
            } catch (err) {
                console.error('Auto-save error:', err);
            }
        };
        reader.readAsDataURL(blob);
    });
}; 
// Runs auto-save every second when content changes are detected
React.useEffect(() => {
    const intervalId = setInterval(() => {
        if (contentChanged.current) {
            autoSaveDocument();
            contentChanged.current = false;
        }
    }, 1000);
    return () => clearInterval(intervalId);
});

// Handles document content change detection
const handleContentChange = (): void => {
    contentChanged.current = true; // Set the ref's current value
};
Enter fullscreen mode Exit fullscreen mode
  1. Download a copy of the document to local storage: When the Download toolbar button is clicked, a copy of the document in the editor will be saved to the local storage.
// Handle toolbar item clicks
const handleToolbarItemClick = (args) => {
    let documentName = containerRef.current.documentEditor.documentName;
    const baseDocName = documentName.replace(/\.[^/.]+$/, '');

    switch (args.item.id) {
        case 'CreateNewDoc':
            setSelectedDocId(0);
            setSelectedDocName(null);
            setShowDialog(true);
            containerRef.current.documentEditor.openBlank();
            containerRef.current.documentEditor.focusIn();
            break;
        case 'OpenFileManager':
            if (fileManagerRef.current) {
                fileManagerRef.current.clearSelection();
                setTimeout(() => {
                    fileManagerRef.current.refreshFiles();
                }, 100);
                containerRef.current.documentEditor.focusIn();
            }
            setShowFileManager(true);
            break;
        case 'DownloadToLocal':
            containerRef.current.documentEditor.save(baseDocName, 'Docx');
            containerRef.current.documentEditor.focusIn();
            break;
        default:
            break;
    }
};
Enter fullscreen mode Exit fullscreen mode
  1. . Create and upload a new document: When the New toolbar item is clicked, a prompt dialog will appear requesting the document name. Once selected, the document will be opened in the Word Document Editor and automatically be saved to the PostgreSQL database.
// Handler for the OK button in the file name prompt dialog with file existence check and save 
// The new file will be automatically saved to PostgreSQL database by the auto-save functionality, which is managed within the setInterval method.  

const handleFileNamePromptOk = async () => {
    const documentName = inputRef.current?.value?.trim();
    if (!documentName) {
        setErrorMessage("Document name cannot be empty.");
        return;
    }

    const baseFilename = `${documentName}.docx`;

    // Check if a document with this name already exists
    const exists = await checkDocumentExistence(baseFilename);
    if (exists) {
        setErrorMessage("A document with this name already exists. Please choose a different name.");
        inputRef.current?.focus();
        inputRef.current?.select();
        return;
    }

    // Proceed with creation
    setErrorMessage("");
    setShowDialog(false);
    const newId = maxDocId + 1;
    setSelectedDocId(newId);
    setMaxDocId(newId);
    setSelectedDocName(baseFilename);
    containerRef.current.documentEditor.documentName = baseFilename;
    containerRef.current.documentEditor.openBlank();
  };
Enter fullscreen mode Exit fullscreen mode

Run the projects

Start the server-side (ASP.NET Core API) first, then run the client-side (React) application to test its functionalities. The output for file operations on the client side will be displayed as shown in the following image.

<alt-text>


React Document Editor with autosave using PostgreSQL

GitHub reference

For the complete source code and working projects, check out the GitHub demo.

Conclusion

Thanks for reading! In this blog, we explored how to efficiently open, edit, and auto-save Word documents to a PostgreSQL Database using Syncfusion’s Document Editor. By following these steps, you can build a scalable and secure document management system that enables users to manage Word documents in the database.

This approach is also adaptable to other database solutions, offering flexibility to meet your application’s specific needs. Try implementing these features in your own project and enhance document handling using PostgreSQL’s robust storage capabilities!

The latest 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 forums, 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)