Building a Docker-Containerized System Automation Desktop Application Using Node.js, Python, and Electron.js

by Yusuf Aweda Jimoh

9 min read

In this article, I'll guide you through building a Docker-containerized system automation desktop application using Node.js, Python, and Electron.js. This project is designed to streamline business automation tasks across various industries, from financial institutions to media companies. 

This Python script automates a basic business task on a Windows machine, such as copying a file from the desktop to the Documents folder.

We'll integrate a React frontend with a Node.js backend that triggers Python scripts for system automation tasks on a Windows machine. Additionally, we'll implement a logging feature to track task completion times and user information. The entire backend will be containerized using Docker to ensure consistent deployment across different environments.

Let's dive in!

Prerequisites

Before advancing into development, make sure you have the following installed:

  • Node.js
  • Docker
  • Python 3
  • Basic knowledge of React, Node.js, and Python

Step 1: Setting Up the React Frontend

The first thing you need to do is to create a simple React application that will serve as the web application frontend of our desktop application.

  1. Let us begin by creating the front-end using “Create React App”:
npx create-react-app automation-app
cd automation-app

     2. Now let us take this further by creating a Basic Login Page. Inside the src directory, replace the contents of App.js with the following code:

import React, { useState } from 'react';
import { BrowserRouter as Router, Route, Routes, useNavigate } from 'react-router-dom';
function LoginPage() {
	const [username, setUsername] = useState('');
	const navigate = useNavigate();
	const handleLogin = () => {
    	localStorage.setItem('username', username);
    	navigate('/dashboard');
	};
	return (
    	<div>
        	<h2>Login</h2>
        	<input
            	type="text"
            	placeholder="Enter Username"
            	value={username}
            	onChange={(e) => setUsername(e.target.value)}
        	/>
        	<button onClick={handleLogin}>Login</button>
    	</div>
	);
}
function Dashboard() {
	const username = localStorage.getItem('username');
	const taskCompletionTime = localStorage.getItem('taskCompletionTime');
	const taskUser = localStorage.getItem('taskUser');
	const handleAutomation = () => {
    	fetch('http://localhost:7000/run-script', {
        	method: 'POST',
    	})
        	.then((response) => response.json())
        	.then((data) => {
            	const timestamp = new Date().toLocaleString();
            	console.log(`Task completed by ${username} at ${timestamp}`);
            	// Store the task completion time and username in local storage
            	localStorage.setItem('taskCompletionTime', timestamp);
            	localStorage.setItem('taskUser', username);
        	})
        	.catch((error) => {
            	console.error('Error:', error);
        	});
	};
	return (
    	<div>
        	<h2>Welcome, {username}</h2>
        	<button onClick={handleAutomation}>Run Automation Task</button>
        	{taskCompletionTime && (
            	<div>
                	<p>Last task completed by {taskUser} at {taskCompletionTime}</p>
            	</div>
        	)}
    	</div>
	);
}
function App() {
	return (
    	<Router>
        	<Routes>
            	<Route path="/" element={<LoginPage />} />
            	<Route path="/dashboard" element={<Dashboard />} />
        	</Routes>
    	</Router>
	);
}
export default App;

We are simply setting up a basic login page where the user can enter their username, which is then stored in the browser's local storage. Upon logging in, the user is redirected to the dashboard where they can trigger an automation task. The dashboard will also display the time of the last completed task and the username of the user who performed it.

To start the front-end app, simply run npm start. This is covered in the section on integrating the front-end and back-end.

Step 2: Setting Up the Node.js Backend

Now, let's set up the backend using Node.js. This backend will include an API that triggers the Python script whenever it is called.

Initialize the Node.js Project: In your project directory, create a new folder for the backend and initialize it:

mkdir backend
cd backend
npm init -y
npm install express cors

Create the API: Now, we'll create an index.js file in the back-end folder with the following content:

const express = require('express');
const { exec } = require('child_process');
const cors = require('cors');

const app = express();
app.use(cors());

app.post('/run-script', (req, res) => {
	exec('python3 ./automation_task.py', (error, stdout, stderr) => {
    	if (error) {
        	console.error(`Error: ${error}`);
        	return res.status(500).json({ error: 'Failed to run script' });
    	}
    	console.log(`Output: ${stdout}`);
    	res.json({ message: 'Task completed successfully' });
	});
});

const PORT = 7000;
app.listen(PORT, () => {
	console.log(`Server running on port ${PORT}`);
});

In this code above, we are setting up an Express server that listens for POST requests on the /run-script endpoint. When this endpoint is called, the server executes a Python script and returns the result.

Step 3: Writing the Python Automation Script

Let's create the Python script that will handle the system automation task, which in this case is copying a file from the desktop to the documents folder. This is just a basic automation. The idea behind this is for you to be creative when implementing this concept for your various needs.

Create the Python Script: In the same backend folder, create a file named automation_task.py:

import os
import shutil

def copy_file():
	desktop = os.path.join(os.path.join(os.environ['USERPROFILE']), 'Desktop')
	documents = os.path.join(os.path.join(os.environ['USERPROFILE']), 'Documents')
	file_to_copy = os.path.join(desktop, 'example.txt')
	destination = os.path.join(documents, 'example.txt')

	try:
    	shutil.copy(file_to_copy, destination)
    	print(f'File copied to {destination}')
	except Exception as e:
    	print(f'Error: {e}')

if __name__ == "__main__":
	copy_file()

This Python script locates a file named example.txt on the user's desktop and copies it to their documents folder. You need to create the file on your desktop for this to work seamlessly.

Step 4: Containerizing the Backend with Docker

To ensure our backend runs consistently across different environments, we will containerize it using Docker.

Create the Dockerfile: In the backend folder, create a file named Dockerfile with the following content:

# Use an official Node.js runtime as a parent image
FROM node:19.0.0

# Install Python, pip, and system dependencies
RUN apt-get update && apt-get install -y \
	python3 \
	python3-pip \
	&& rm -rf /var/lib/apt/lists/*  # Clean up to reduce image size

# Set the working directory in the container
WORKDIR /FileAutomation

# Install Node.js dependencies
COPY package*.json ./
RUN npm install --only=production

# Bundle app source
COPY . .

# Expose the app port
EXPOSE 7000

# Define the command to run your app
CMD ["node", "index.js"]

Create the docker-compose.yml File: In the root of the backend folder, create a docker-compose.yml file:

version: '3'
services:
  web:
	build: .
	ports:
  	- "7000:7000"

The docker-compose.yml file defines the service that will run our back-end container and maps port 7000 on the container to port 7000 on the host machine.

Build and Run the Docker Container: Navigate to the backend directory and run the following commands:

docker-compose up --build -d

This will build the Docker image in detached mode and start the container. The back-end server will be running and ready to handle API requests.

Step 5: Integrating Frontend and Backend

With both the front-end and back-end set up, the next step is to ensure they work together.Run the React Frontend: In the automation-app directory, start the React app:

npm start

This command does the following:

  1. Compiles the Application: Our application now starts on the development server. It compiles the React app, and opens it in your default web browser at http://localhost:3000.

2. Run the Backend: Navigate to the back-end Directory. Make sure you're in the backend folder where the index.js file is located and run the command:

cd backend

3. Start the Backend Server: Use the following command to start the backend server:

Node index.js

This command will start the Express server, and you should see an output in the terminal indicating that the server is running:

Server running on port 7000

4. Trigger the Automation Task: Access the React app in your browser, log in, and navigate to the dashboard. Click the "Run Automation Task" button. This will send a request to the backend, which will, in turn, execute the Python script to copy the file.

5. Verify the Result: After running the automation task, check the documents folder on your system to ensure the file has been copied. The front-end will also log the time the task was completed and store it along with the username in the browser’s local storage.

Step 6: Converting the React Front-end into a Desktop Application Using Electron.js

Now, we'll do something very interesting. After building the React front-end, we'll convert it into a desktop application using Electron.js. This method involves downloading Electron, and then copying the built React files into the Electron folder, and configuring Electron to load the index.html file.

Download Electron: Start by downloading Electron in your React project directory:

npm install electron --save-dev

Set Up Electron: Create a new folder in your project directory named electron-app and create a main.js file inside it:

const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
	const win = new BrowserWindow({
    	width: 800,
    	height: 600,
    	webPreferences: {
        	nodeIntegration: true,
        	contextIsolation: false,
    	},
	});
	win.loadFile(path.join(__dirname, 'index.html'));
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') {
    	app.quit();
	}
});
app.on('activate', () => {
	if (BrowserWindow.getAllWindows().length === 0) {
    	createWindow();
	}
});

Copy the Build Files: Copy all the contents from the build folder generated by React into the electron-app folder. Ensure that index.html and other static files are in the root of electron-app.

Run Electron: Navigate to the electron-app directory and start Electron:

npx electron .

Final Word

In this article, we walked through the process of building a Docker-containerized system automation desktop application using Node.js, Python, and Electron.js. 

We started by setting up and building a React frontend, creating a Node.js backend to trigger a Python automation script, and finally converting the React application into a desktop app using Electron.js. 

By following this approach, you can create a robust and scalable desktop application that runs consistently across different environments.

FAQs

Q: What is the purpose of containerizing the backend with Docker?
Containerizing the backend ensures that the application runs consistently across different environments by isolating the Node.js and Python components, making deployment easier and more reliable.
Q: Why use Electron.js to convert a React app into a desktop app?
Electron.js allows you to package web technologies like React into a cross-platform desktop application, providing a native-like experience while maintaining the flexibility of web development.
Q: How does the automation task work in this app?
The automation task triggers a Python script from the Node.js backend, which performs system automation tasks like copying files from the desktop to the Documents folder on a Windows machine.
Yusuf Aweda Jimoh
Yusuf Aweda Jimoh
Senior Software Engineer

Yusuf Aweda is a Lead Software Engineer with 17 years of experience in IT, specializing in software development, generative AI, machine learning, and data analytics. He's also a technical writer, speaker, and DeepRacer reinforcement learning engineer.

Expertise
  • AWS
  • Azure
  • GCP
  • ReactJS
  • React Native
  • +3

Ready to start?

Get in touch or schedule a call.