DEV Community

Mike
Mike

Posted on

Guide: Integrate a Self-Hosted File Server with Your Web Page

When building an application (frontend or backend) that requires file management, developers typically face two choices: build a custom feature or purchase an enterprise management service.

Building a complete file management system or Content Management System (CMS) can be a significant time investment, especially when several robust, premium enterprise solutions already exist. However, while paying for an existing enterprise solution seems easier, the pricing tiers often become a major consideration.

This tutorial guide will offer developers an alternative by walking through how to quickly integrate a self-hosted file management application with a ReactJS application.

To follow along with this tutorial, you should have:

  • Docker Engine installed
  • Working knowledge of JavaScript and ReactJS
  • General web development experience

Download Self-Hosted File Server (Free)

Here are the steps for setting up your self-hosted, Docker-based file server using the free BoltonSea Community Service.

Please follow these steps to deploy the service. You can also refer to the quick installer guide available at [Link to Guide] for additional details.

  1. Pull/download the BoltonSea Community Service Image from DockerHub

    • docker pull boltonsea/cms_api:latest
  2. Verify the Image Download

    • docker images boltonsea/cms_api
  3. Run the container, exposing the service on port 9080 of your host machine (mapping to port 9200 on the container).

    • docker run -d --name myBoltonSeaCMS -p 9080:9200 boltonsea/cms_api

Our File Service now Live

  • The file service is available here: http://localhost:9080

  • Test an endpoint (via Chrome Browser): http://localhost:9080/organizations/1

Testing https://boltonsea.com Live Server

Create the React Application

We will begin by creating the base React application using the following commands:

npx create-react-app my-app
cd my-app
Enter fullscreen mode Exit fullscreen mode

Install Dependencies and Start the UI

npm install ajv --save-dev
npm start
Enter fullscreen mode Exit fullscreen mode

The command terminal should display the following output, confirming successful compilation and providing the local access details:

Compiled successfully!

You can now view file-upload-app in the browser.

 Local:            http://localhost:3000
 On Your Network:  http://192.168.4.31:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

webpack compiled successfully
Enter fullscreen mode Exit fullscreen mode

Now, let's test our react UI. It should look like this
ReactJS Default UI

Integrate with File Service API

To begin the UI integration process, please set up the necessary files:

  1. Navigate to the my-app/src folder.
  2. Create the file named lib.js within that directory.

This is the template of the lib.js file.

// ---------------- DEFAULT CONSTANT DECLARATIONs  ----------------

const organizationId = 1;
const mediaGroupId = 1;

// ------ Endpoint to our docker-based BoltonSea File Server ------

const FILE_SERVER_ENDPOINT = "http://localhost:9080";
const UPLOAD_ENDPOINT = `${FILE_SERVER_ENDPOINT}/media`;
const MEDIA_GROUP_ENDPOINT = `${FILE_SERVER_ENDPOINT}/mediagroup/${mediaGroupId}/content`;

// ----------------------------------
Enter fullscreen mode Exit fullscreen mode
// function to upload files into the filer_server
export async function uploadFile(file) {
… …
}

// function to get files from the filer_server
export async function fetchData() {
… …
}

const mediaGroupContent = (content) => {
… …
}
Enter fullscreen mode Exit fullscreen mode

We can see that there are three helper method function definitions here.

This method helps upload files to the file-server
function uploadFile(file)

This method helps fetch the list of files from the file-server
function fetchData()

This is a helper method to extract an API requests output
const mediaGroupContent = (content)

Copy the full content of the lib.js file.

// ---------------- DEFAULT CONSTANT DECLARATIONs  ----------------

const organizationId = 1;
const mediaGroupId = 1;

// ---------------- Endpoint to our docker-based BoltonSea File Server ----------------

const FILE_SERVER_ENDPOINT = "http://localhost:9080";
const UPLOAD_ENDPOINT = `${FILE_SERVER_ENDPOINT}/media`;
const MEDIA_GROUP_ENDPOINT = `${FILE_SERVER_ENDPOINT}/mediagroup/${mediaGroupId}/content`;

// --------------------------------------------------------------------

export async function uploadFile(file) {
  // Create an object of formData
  const formData = new FormData();

  // Update the formData object
  formData.append("media_group_id", mediaGroupId);
  formData.append("account_id", organizationId);
  formData.append("file", file);

  try {
    const response = await fetch(UPLOAD_ENDPOINT, {
      method: "POST",
      body: formData,
    });

    if (response.ok) {
      alert("File uploaded successfully!");
      window.location.reload();
    } else {
      console.error("File upload failed:", response.statusText);
      alert("File upload failed!");
    }
  } catch (error) {
    console.error("Error during file upload:", error);
    alert("An error occurred during file upload.");
  }
}

export async function fetchData() {
  let result = [];
  try {
    const response = await fetch(MEDIA_GROUP_ENDPOINT); // Replace with your API URL
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    result = await response.json();
    result = mediaGroupContent(result.content);
  } catch (error) {
    console.error("Error Getting Response");
  }

  return result;
}

const mediaGroupContent = (content) => {
  content = content.filter((item) => item != null);
  return content.map((mediaItem) => {
    let type = "document";

    if (mediaItem?.mime_type?.includes("image")) {
      type = "image";
    }
    mediaItem["type"] = type;

    return mediaItem;
  });
};

Enter fullscreen mode Exit fullscreen mode

Please follow these steps to update the application files:

Navigate to App.js

  1. Update App.js:
    • Copy the contents.
    • Navigate to the App.js file and paste the copied contents into the file.
    • Refresh the user interface (UI).

Refresh the UI

import { useEffect, useState } from "react";
import { uploadFile, fetchData } from "./lib";

import logo from "./logo.svg";
import "./App.css";

let file_ = null;

function App() {
  const [mediaList, setMediaList] = useState([]);

  useEffect(() => {
    fetchData()
      .then(setMediaList)
      .catch((e) => console.error("An error occured!", e));
  }, []);

  const handleChange = async (e) => {
    file_ = e.target.files[0];
  };

  const uploadAction = async (e) => {
    e.preventDefault();

    if (file_) {
      await uploadFile(file_);
      file_ = null;
    }
  };

  return (
    <div className="App">
      <div id="page_title">
        <img src={logo} className="App-logo" alt="logo" height={100} />
        <h1>Document Management Dashboard</h1>
      </div>

      <div id="upload-component" className="container">
        <div className="input-group">
          <input
            type="file"
            onChange={handleChange}
            className="form-control"
            id="fileInput"
            aria-describedby="uploadButton"
            aria-label="Upload"
          />
          <button
            className="btn btn-outline-primary"
            type="button"
            id="uploadButton"
            onClick={uploadAction}
          >
            Upload
          </button>
        </div>
      </div>

      <div id="media_list" className="container text-center">
        <div className="row">
          {mediaList?.map((item, i) => (
            <div key={i} className="col-12 col-sm-6 col-lg-4">
              <div className="media_item">
                {item.type === "image" && (
                  <img src={item.public_url} alt={item.original_file_name} />
                )}

                <div>
                  <a target="_blank" rel="noreferrer" href={item.public_url}>
                    {item.original_file_name}
                  </a>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
  1. Navigate to App.css
    • Copy the contents of the methods.
    • Navigate to the App.css file and paste the copied contents into the file.
    • Refresh the user interface (UI).

Refresh the UI

@import url("https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css");

body {
  background-color: white;
  font-family: "Roboto", sans-serif;
}
h1 {
  color: #444;
  text-align: center;
}

button {
  background-color: #6161d4;
}

/* ------- Other Components ------- */

#page_title {
  margin: 6rem;
  text-align: center;
}

#upload-component {
  margin: 3rem auto;
  max-width: 50rem;
}

#media_list {
  padding: 1rem 2rem;
  background-color: #f8f8f8;
}

.media_item {
  height: 15rem;
  overflow: scroll;
  margin: 1.5rem 1rem;
  border-radius: 5px;
  align-content: center;
  background-color: #fff;
  box-shadow: 0 0 5px #ccc; /* A black shadow, offset, blurred, and semi-transparent */
}

.media_item img {
  max-height: 80%;
  margin-bottom: 1em;
}

Enter fullscreen mode Exit fullscreen mode

Live Test Our File-Service UI Dashboard

Web Dashboard to test BoltonSea Content Management System

We need to conduct a test of our File Service using the Dashboard interface.

Please upload a few test files directly through the Dashboard UI and then verify that they can be successfully retrieved afterward. This will help us confirm that the upload and retrieval processes are functioning correctly.


Here is what my Dashboard looks like 👇 after my file uploads through the Dashboard.

Final view of the dashboard with BoltonSea File Service

Tools


Happy Coding!

Top comments (0)