Step-by-Step Guide to building a custom automated database backup engine block for Gutenberg using Vue micro-frontends
Project Setup: Vue Micro-Frontend and WordPress Plugin Architecture
This guide details the construction of a custom Gutenberg block that leverages a Vue.js micro-frontend to manage automated database backups. We’ll focus on a robust, production-ready implementation, starting with the foundational plugin structure and the Vue application’s integration.
Our approach involves creating a standalone Vue application that communicates with a WordPress backend via REST API endpoints. This micro-frontend will be enqueued as a JavaScript asset within the WordPress admin area, specifically when our custom Gutenberg block is active.
WordPress Plugin Boilerplate
Begin by scaffolding a basic WordPress plugin. This will house our Gutenberg block registration, asset enqueuing, and REST API endpoint definitions.
<?php
/**
* Plugin Name: Automated DB Backup Block
* Description: A custom Gutenberg block for managing database backups with a Vue micro-frontend.
* Version: 1.0.0
* Author: Antigravity
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: automated-db-backup
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Enqueue Gutenberg block assets.
*/
function adb_enqueue_block_assets() {
// Register block editor script.
wp_enqueue_script(
'adb-block-editor-script',
plugin_dir_url( __FILE__ ) . 'build/index.js', // Path to your compiled Vue app
array( 'wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n' ),
filemtime( plugin_dir_path( __FILE__ ) . 'build/index.js' )
);
// Enqueue Vue app styles if any
// wp_enqueue_style(
// 'adb-vue-styles',
// plugin_dir_url( __FILE__ ) . 'build/style.css', // Path to your compiled Vue app styles
// array(),
// filemtime( plugin_dir_path( __FILE__ ) . 'build/style.css' )
// );
// Localize script with REST API URL and nonce
wp_localize_script(
'adb-block-editor-script',
'adb_data',
array(
'rest_url' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
)
);
}
add_action( 'enqueue_block_editor_assets', 'adb_enqueue_block_assets' );
/**
* Register the Gutenberg block.
*/
function adb_register_block() {
register_block_type( 'automated-db-backup/backup-manager', array(
'editor_script' => 'adb-block-editor-script',
'render_callback' => 'adb_render_backup_manager_block',
) );
}
add_action( 'init', 'adb_register_block' );
/**
* Render callback for the block (optional, if you need server-side rendering).
* For a micro-frontend, this might just output a placeholder div.
*/
function adb_render_backup_manager_block( $attributes ) {
return '<div id="adb-backup-manager-app"></div>';
}
/**
* Register REST API endpoints for backup management.
*/
require_once plugin_dir_path( __FILE__ ) . 'includes/rest-api.php';
Vue Micro-Frontend Setup
We’ll use Vue CLI to set up our micro-frontend. The key is to configure the build process to output a single JavaScript file that can be enqueued by WordPress.
1. **Install Vue CLI:**
npm install -g @vue/cli
2. **Create Vue Project:**
vue create ../automated-db-backup-vue-app cd ../automated-db-backup-vue-app
3. **Configure `vue.config.js` for WordPress:**
This configuration is crucial. We’ll set the output to a library format and specify the output directory to match our WordPress plugin’s `build` folder. This allows WordPress to directly enqueue the compiled JavaScript.
// ../automated-db-backup-vue-app/vue.config.js
module.exports = {
// Set output directory to match WordPress plugin's build folder
// Adjust path if your Vue app is not in a sibling directory
outputDir: '../automated-db-backup/build',
// Configure for library mode to output a single JS file
chainWebpack: config => {
config.externals({
// WordPress dependencies are globally available in the admin
'react': 'wp.element', // For Gutenberg components if needed
'react-dom': 'wp.element',
'wp.blocks': 'wp.blocks',
'wp.element': 'wp.element',
'wp.editor': 'wp.editor',
'wp.components': 'wp.components',
'wp.i18n': 'wp.i18n',
});
config.output.libraryTarget('var'); // Use 'var' for global scope
config.output.library('AdbBackupApp'); // Global variable name
},
// Disable CSS extraction to keep everything in one JS file for simplicity
// For production, you might want to extract CSS separately.
css: {
extract: false,
},
// Configure Babel for broader compatibility if needed
// babel: {
// presets: [
// '@vue/cli-plugin-babel/preset'
// ]
// }
};
4. **Modify `main.js` to mount into a specific DOM element:**
// ../automated-db-backup-vue-app/src/main.js
import { createApp } from 'vue';
import App from './App.vue';
// Import necessary WordPress components if you're using them directly in Vue
// import { Button } from '@wordpress/components';
// Ensure the DOM element exists before mounting
document.addEventListener('DOMContentLoaded', () => {
const appElement = document.getElementById('adb-backup-manager-app');
if (appElement) {
const app = createApp(App);
// Register global components or plugins if needed
// app.component('wp-button', Button);
app.mount('#adb-backup-manager-app');
} else {
console.error('Element with ID "adb-backup-manager-app" not found.');
}
});
Gutenberg Block Registration and Vue Integration
Now, let’s define the Gutenberg block itself. This block will act as a container for our Vue application. We’ll use `registerBlockType` from `@wordpress/blocks`.
// ../automated-db-backup-vue-app/src/index.js (or a dedicated block file)
const { registerBlockType } = wp.blocks;
const { createElement } = wp.element;
const { InspectorControls, RichText } = wp.editor;
const { PanelBody, TextControl, Button } = wp.components;
const { __ } = wp.i18n;
// Import your Vue App component
import App from './App.vue';
// This is the entry point for the Gutenberg editor side.
// We'll render a placeholder div here that our Vue app will mount onto.
registerBlockType('automated-db-backup/backup-manager', {
title: __('Database Backup Manager', 'automated-db-backup'),
icon: 'database', // WordPress dashicon
category: 'widgets', // Or 'common', 'design', etc.
attributes: {
// Define any attributes your block might need, e.g., for settings
// For a micro-frontend, attributes might be minimal or non-existent
// if all state is managed within the Vue app.
},
edit: function(props) {
const { attributes, setAttributes } = props;
// The edit function should render the placeholder for the Vue app.
// The Vue app itself will handle the actual UI rendering.
// We ensure the element ID matches what main.js expects.
return createElement(
'div',
{
id: 'adb-backup-manager-app',
style: {
border: '1px dashed #ccc',
padding: '20px',
textAlign: 'center',
minHeight: '100px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f9f9f9',
}
},
__('Loading Backup Manager...', 'automated-db-backup')
);
},
save: function(props) {
// The save function should return the markup that will be saved to the post content.
// For a dynamic block rendered server-side, this might be empty or a placeholder.
// If using a render_callback in PHP, this function might not be strictly necessary
// for the frontend display, but it's good practice for editor preview.
// We return the placeholder div that the PHP render_callback will use.
return createElement('div', { id: 'adb-backup-manager-app' });
},
});
// Note: The actual Vue app mounting is handled in main.js, which is triggered
// when this script is enqueued by WordPress. The 'edit' function here ensures
// the placeholder div exists in the editor.
Backend: REST API Endpoints for Backup Operations
The Vue micro-frontend will interact with WordPress via the REST API. We need to define custom endpoints for triggering backups, retrieving backup logs, scheduling, etc.
Create a new file, e.g., `includes/rest-api.php`, within your plugin directory.
<?php
/**
* REST API Endpoints for Automated DB Backup.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// --- Backup Trigger Endpoint ---
register_rest_route( 'adb/v1', '/backup/trigger', array(
'methods' => WP_REST_Server::CREATABLE, // Use CREATABLE for POST requests
'callback' => 'adb_rest_trigger_backup',
'permission_callback' => function () {
// Ensure user has capability to manage options or a custom capability
return current_user_can( 'manage_options' );
},
) );
function adb_rest_trigger_backup( WP_REST_Request $request ) {
// --- Security Check: Verify nonce ---
if ( ! isset( $request->_wpnonce ) || ! wp_verify_nonce( $request->_wpnonce, 'wp_rest' ) ) {
return new WP_Error( 'rest_nonce_invalid', esc_html__( 'Nonce is invalid.', 'automated-db-backup' ), array( 'status' => 403 ) );
}
// --- Backup Logic ---
// This is a simplified example. In production, you'd use WP-CLI or a more robust method.
$backup_result = adb_perform_database_backup();
if ( is_wp_error( $backup_result ) ) {
return new WP_Error( 'backup_failed', $backup_result->get_error_message(), array( 'status' => 500 ) );
}
// --- Log the backup ---
adb_log_backup_event( 'manual_trigger', 'success', $backup_result );
return new WP_REST_Response( array(
'success' => true,
'message' => esc_html__( 'Database backup initiated successfully.', 'automated-db-backup' ),
'backup_info' => $backup_result,
), 200 );
}
// --- Backup Log Endpoint ---
register_rest_route( 'adb/v1', '/backup/logs', array(
'methods' => WP_REST_Server::READABLE, // GET requests
'callback' => 'adb_rest_get_backup_logs',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
) );
function adb_rest_get_backup_logs( WP_REST_Request $request ) {
// --- Security Check: Verify nonce ---
if ( ! isset( $request->_wpnonce ) || ! wp_verify_nonce( $request->_wpnonce, 'wp_rest' ) ) {
return new WP_Error( 'rest_nonce_invalid', esc_html__( 'Nonce is invalid.', 'automated-db-backup' ), array( 'status' => 403 ) );
}
$logs = adb_get_backup_log_entries( 50 ); // Get last 50 entries
return new WP_REST_Response( array(
'success' => true,
'logs' => $logs,
), 200 );
}
// --- Placeholder Functions (Implement these robustly) ---
/**
* Performs the actual database backup.
* Returns an array with backup details or a WP_Error object.
*/
function adb_perform_database_backup() {
// In a real-world scenario, use WP-CLI for reliability and features:
// Example using WP-CLI (requires WP-CLI installed and accessible):
// $command = 'wp db export --path=' . ABSPATH;
// $output = shell_exec($command . ' 2>&1');
// if (strpos($output, 'Success:') !== false) {
// // Parse output to get filename, size, etc.
// $filename = ...; // Extract from $output
// $filesize = ...; // Extract from $output
// return ['filename' => $filename, 'size' => $filesize, 'timestamp' => current_time('mysql')];
// } else {
// return new WP_Error('wp_cli_error', 'WP-CLI backup failed: ' . $output);
// }
// --- Basic manual backup (less robust, for demonstration) ---
global $wpdb;
$backup_file = WP_CONTENT_DIR . '/uploads/db-backup-' . date('Ymd-His') . '.sql';
$tables = $wpdb->get_col( 'SHOW TABLES' );
$handle = fopen( $backup_file, 'w+' );
if ( ! $handle ) {
return new WP_Error( 'file_open_error', 'Could not open backup file for writing.' );
}
// Add SQL header
fwrite( $handle, "-- WordPress Database Backup\n" );
fwrite( $handle, "-- Generated on: " . date('Y-m-d H:i:s') . "\n" );
fwrite( $handle, "SET FOREIGN_KEY_CHECKS=0;\n" );
fwrite( $handle, "SET SQL_MODE = \"NO_AUTO_VALUE_ON_ZERO\";\n" );
foreach ( $tables as $table ) {
$table_name = $wpdb->prefix . $table; // Ensure correct prefix
$result = $wpdb->get_results( "SHOW CREATE TABLE `" . $table_name . "`" );
$create_sql = $result[0]->'Create Table';
fwrite( $handle, "\n" . $create_sql . ";\n" );
$rows = $wpdb->get_results( "SELECT * FROM `" . $table_name . "`" );
foreach ( $rows as $row ) {
$insert_sql = "INSERT INTO `" . $table_name . "` VALUES (";
$values = array();
foreach ( $row as $column_value ) {
$values[] = $wpdb->prepare( '%s', $column_value ); // Use prepare for safety
}
$insert_sql .= implode( ',', $values );
$insert_sql .= ");\n";
fwrite( $handle, $insert_sql );
}
}
fwrite( $handle, "SET FOREIGN_KEY_CHECKS=1;\n" );
fclose( $handle );
if ( file_exists( $backup_file ) ) {
$filesize = filesize( $backup_file );
return [
'filename' => basename( $backup_file ),
'size' => size_format( $filesize ),
'path' => $backup_file,
'timestamp' => current_time('mysql')
];
} else {
return new WP_Error( 'backup_creation_failed', 'Backup file was not created.' );
}
}
/**
* Logs backup events.
* In a real scenario, use a custom database table or a dedicated log file.
*/
function adb_log_backup_event( $type, $status, $details = array() ) {
$log_entry = array(
'timestamp' => current_time( 'mysql' ),
'type' => $type,
'status' => $status,
'details' => $details,
);
// For simplicity, store in options. Not recommended for high volume.
$logs = get_option( 'adb_backup_logs', array() );
$logs[] = $log_entry;
// Keep only the last N logs
update_option( 'adb_backup_logs', array_slice( $logs, -50 ) ); // Keep last 50
}
/**
* Retrieves backup log entries.
*/
function adb_get_backup_log_entries( $limit = 50 ) {
$logs = get_option( 'adb_backup_logs', array() );
return array_slice( $logs, -$limit );
}
// --- Add custom capability for backup management ---
function adb_add_custom_capabilities() {
$role = get_role( 'administrator' );
if ( $role && ! $role->has_cap( 'manage_backup_options' ) ) {
$role->add_cap( 'manage_backup_options' );
}
}
add_action( 'admin_init', 'adb_add_custom_capabilities' );
// --- Hook into plugin activation to add capability ---
register_activation_hook( __FILE__, 'adb_add_custom_capabilities' );
// --- Modify permission callback to use custom capability ---
// This is a more advanced way to handle permissions.
// For simplicity, the register_rest_route callbacks above use 'manage_options'.
// You could replace 'manage_options' with 'manage_backup_options' after adding it.
Vue Application Development
Now, let’s build the Vue components for the backup manager interface. This will involve making API calls to the WordPress REST API endpoints we just defined.
1. `App.vue` (Root Component):
<template>
<div id="adb-backup-app">
<h2>{{ $t('Database Backup Manager') }}</h2>
<div v-if="loading" class="adb-loading">{{ $t('Loading...') }}</div>
<div v-else>
<button @click="triggerBackup" :disabled="isBackingUp" class="adb-button primary">
{{ isBackingUp ? $t('Backing up...') : $t('Trigger Manual Backup') }}
</button>
<p v-if="backupMessage" :class="{'error': backupError}">{{ backupMessage }}</p>
<h3>{{ $t('Backup Logs') }}</h3>
<div v-if="logs.length === 0">{{ $t('No backup logs found.') }}</div>
<ul v-else class="adb-log-list">
<li v-for="(log, index) in logs" :key="index">
<strong>{{ log.timestamp }}</strong> - {{ log.type }} ({{ log.status }})
<pre v-if="log.details && log.details.filename">{{ log.details.filename }} ({{ log.details.size }})</pre>
</li>
</ul>
</div>
</div>
</template>
<script>
import { ref, onMounted, computed } from 'vue';
import axios from 'axios'; // Use axios for API requests
import { __ } from '@wordpress/i18n'; // WordPress i18n function
export default {
name: 'App',
setup() {
const loading = ref(true);
const isBackingUp = ref(false);
const backupMessage = ref('');
const backupError = ref(false);
const logs = ref([]);
// Access WordPress localized data
const restUrl = window.adb_data.rest_url;
const nonce = window.adb_data.nonce;
const triggerBackup = async () => {
isBackingUp.value = true;
backupMessage.value = '';
backupError.value = false;
try {
const response = await axios.post(`${restUrl}adb/v1/backup/trigger`, {
_wpnonce: nonce, // Include nonce for verification
});
backupMessage.value = response.data.message;
if (response.data.success) {
fetchLogs(); // Refresh logs after successful backup
} else {
backupError.value = true;
backupMessage.value = response.data.message || __('Backup failed. Please check server logs.', 'automated-db-backup');
}
} catch (error) {
backupError.value = true;
let errorMessage = __('An unexpected error occurred.', 'automated-db-backup');
if (error.response) {
errorMessage = error.response.data.message || error.response.statusText;
} else if (error.request) {
errorMessage = __('No response from server.', 'automated-db-backup');
}
backupMessage.value = errorMessage;
console.error('Backup trigger error:', error);
} finally {
isBackingUp.value = false;
}
};
const fetchLogs = async () => {
try {
const response = await axios.get(`${restUrl}adb/v1/backup/logs`, {
params: {
_wpnonce: nonce, // Include nonce for verification
},
});
if (response.data.success) {
logs.value = response.data.logs || [];
}
} catch (error) {
console.error('Error fetching logs:', error);
backupMessage.value = __('Failed to load backup logs.', 'automated-db-backup');
backupError.value = true;
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchLogs();
});
// Provide translations
const $t = (key) => __(key, 'automated-db-backup');
return {
loading,
isBackingUp,
backupMessage,
backupError,
logs,
triggerBackup,
fetchLogs,
$t,
};
},
};
</script>
<style scoped>
.adb-loading {
color: #777;
font-style: italic;
}
.adb-button {
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
margin-right: 10px;
}
.adb-button.primary {
background-color: #0073aa;
color: white;
}
.adb-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.error {
color: red;
font-weight: bold;
}
.adb-log-list {
list-style: none;
padding: 0;
margin-top: 15px;
border: 1px solid #eee;
padding: 10px;
max-height: 300px;
overflow-y: auto;
}
.adb-log-list li {
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px dashed #eee;
font-size: 0.9em;
}
.adb-log-list li:last-child {
border-bottom: none;
}
.adb-log-list pre {
background-color: #f0f0f0;
padding: 5px;
border-radius: 3px;
font-size: 0.8em;
white-space: pre-wrap; /* Allow wrapping */
word-break: break-all; /* Break long words */
margin-top: 5px;
}
h2, h3 {
margin-bottom: 15px;
}
</style>
2. Build Process:**
Navigate to your Vue app directory and run the build command:
cd ../automated-db-backup-vue-app
npm run build
This will generate the `index.js` file (and potentially `index.css` if not disabled) in the `../automated-db-backup/build` directory, ready to be enqueued by WordPress.
Deployment and Usage
1. **Install the Plugin:** Place the `automated-db-backup` folder into your WordPress `wp-content/plugins/` directory.
2. **Activate the Plugin:** Go to the WordPress admin area -> Plugins and activate "Automated DB Backup Block".
3. **Add the Block:** Edit any page or post, open the Gutenberg editor, and search for the "Database Backup Manager" block. Add it to your content.
4. **Interact:** The Vue application will load within the block's area in the editor. You can now trigger backups and view logs directly from the WordPress admin.
Advanced Considerations and Next Steps
- Error Handling & Logging: Implement more robust logging (e.g., custom database table, file logging) and detailed error reporting within the Vue app and backend.
- Scheduling: Add functionality to schedule backups using WordPress cron (`wp_schedule_event`) or an external cron job triggering a WP-CLI command.
- Backup Storage: Integrate with cloud storage services (S3, Google Cloud Storage, Dropbox) for off-site backups.
- Security: Harden REST API endpoints, implement stricter role-based access control, and ensure secure handling of backup files. Consider encrypting backups.
- Configuration: Allow users to configure backup settings (e.g., tables to include/exclude, compression) via the Vue interface, storing these in WordPress options.
- WP-CLI Integration: For production environments, strongly prefer WP-CLI for database operations. It's more reliable, feature-rich, and handles edge cases better than manual SQL generation.
- Vue Build Optimization: For larger Vue apps, consider code splitting, lazy loading components, and optimizing assets for production builds. Extracting CSS might be beneficial.
- State Management: For more complex Vue applications, consider using Pinia or Vuex for centralized state management.