Перейти к основному содержимому
заметка

The complete demo source code is available on GitHub: https://github.com/DHTMLX/gantt-migrating-from-devexpress.

Migrating from DevExpress Gantt to DHTMLX Gantt

Introduction

This guide will walk you through the process of migrating an existing application from DevExpress Gantt to DHTMLX Gantt. We'll cover all necessary steps including database schema changes, server-side API modifications, and client-side code updates.

Prerequisites

Before starting the migration, ensure you have:

  • An existing working application using DevExpress Gantt
  • Node.js (>= 20.0.0) installed
  • MySQL database with DevExpress data structure
  • Basic knowledge of Express.js, React, and TypeScript

Step 1: Database Migration

Understanding DevExpress Schema

If you followed the DevExpress demo setup, you should have two tables: devexpress_tasks and devexpress_dependencies.

The devexpress_tasks table structure:

DevExpress tasks table

The devexpress_dependencies table structure:

DevExpress links table

This two-table structure is already similar to DHTMLX's approach, making the migration straightforward.

Create DHTMLX Tables

Create two new tables compatible with DHTMLX Gantt:

CREATE TABLE IF NOT EXISTS gantt_tasks (
id INT(11) NOT NULL AUTO_INCREMENT,
text VARCHAR(255) NOT NULL,
start_date DATETIME NOT NULL,
end_date DATETIME NOT NULL,
progress FLOAT NOT NULL DEFAULT 0,
parent INT(11) NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS gantt_links (
id INT(11) NOT NULL AUTO_INCREMENT,
source INT(11) NOT NULL,
target INT(11) NOT NULL,
type VARCHAR(1) NOT NULL,
PRIMARY KEY (id)
);

Note: DHTMLX Gantt will automatically calculate duration based on start_date and end_date.

Migrate Existing Data

Now migrate your existing DevExpress data to the new DHTMLX tables.

Migrate tasks:

INSERT INTO gantt_tasks (id, text, start_date, end_date, progress, parent)
SELECT
id,
title,
start,
end,
progress / 100,
COALESCE(parentId, 0)
FROM devexpress_tasks;

Migrate links (dependencies):

DevExpress already stores dependencies in a structured format in the devexpress_dependencies table, which makes migration straightforward:

INSERT INTO gantt_links (id, source, target, type)
SELECT
id,
predecessorId, -- predecessorId → source
successorId, -- successorId → target
CASE type
WHEN 0 THEN '0' -- Finish-to-Start
WHEN 1 THEN '1' -- Start-to-Start
WHEN 2 THEN '2' -- Finish-to-Finish
WHEN 3 THEN '3' -- Start-to-Finish
ELSE '0'
END
FROM devexpress_dependencies;

You can verify that the data was migrated correctly by running the following commands:

SELECT * FROM gantt_tasks;
SELECT * FROM gantt_links;

You should see all your tasks and links properly transferred with the correct field mappings.

Mapping DevExpress Task Fields to DHTMLX Gantt

DevExpress FieldDHTMLX FieldNotes
ididTask ID
titletextTask name
startstart_dateTask start date and time
endend_dateTask end date and time
progressprogressDevExpress: 0-100 (integer), DHTMLX: 0-1 (float). Divide by 100 during migration
parentIdparentParent task ID. NULL values → 0 for root tasks

More about task properties: Task Properties.

DevExpress FieldDHTMLX FieldNotes
ididLink ID
predecessorIdsourceID of the task that the dependency starts from
successorIdtargetID of the task that the dependency points to
typetypeDependency type. DevExpress uses numbers (0-3), DHTMLX uses strings ("0"-"3") by default

More about link properties: Link Properties.

Step 2: Backend Migration (server.js)

Remove DevExpress Endpoints

Delete the following DevExpress-specific endpoints from your server.js:

  • app.get('/api/tasks', ...) - DevExpress tasks loading endpoint
  • app.post('/api/tasks', ...) - Create task endpoint
  • app.put('/api/tasks/:id', ...) - Update task endpoint
  • app.delete('/api/tasks/:id', ...) - Delete task endpoint
  • app.get('/api/dependencies', ...) - DevExpress dependencies loading endpoint
  • app.post('/api/dependencies', ...) - Create dependency endpoint
  • app.put('/api/dependencies/:id', ...) - Update dependency endpoint
  • app.delete('/api/dependencies/:id', ...) - Delete dependency endpoint

Also remove the CustomStore-related response format handling.

Install DHTMLX Gantt Packages

Remove DevExpress dependencies:

npm uninstall devextreme devextreme-react

Install DHTMLX React Gantt following the installation guide.

For this tutorial, we will use the trial version of DHTMLX React Gantt:

npm install @dhtmlx/trial-react-gantt

Install date formatting library for MySQL DATETIME conversion:

npm install date-format-lite

Add Data Loading Endpoint

Add the GET endpoint to load data in DHTMLX format. Import the date-format-lite library at the top of your server.js:

import dateFormat from 'date-format-lite';

Then add the data loading endpoint:

// GET /load - Load all tasks and links
app.get('/load', async (req, res) => {
try {
const [tasks] = await pool.query('SELECT * FROM gantt_tasks ORDER BY id');
const [links] = await pool.query('SELECT * FROM gantt_links');

tasks.forEach((task) => {
if (task.start_date) {
task.start_date = task.start_date.format('YYYY-MM-DD hh:mm:ss');
}
if (task.end_date) {
task.end_date = task.end_date.format('YYYY-MM-DD hh:mm:ss');
}
});

res.json({
data: tasks,
links: links,
});
} catch (error) {
console.error('Error loading data:', error);
res.status(500).json({ error: 'Failed to load data' });
}
});

DevExpress returns separate arrays, DHTMLX expects { data: [...], links: [...] }.

DHTMLX React Gantt uses a custom save handler to synchronize data with the server. Each operation (create, update, delete) is sent with the appropriate HTTP method.

Add handlers for task operations:

// POST /save/task - Create a new task
app.post('/save/task', async (req, res) => {
try {
const task = getTask(req.body);

const [result] = await pool.query(
'INSERT INTO gantt_tasks (text, start_date, end_date, progress, parent) VALUES (?, ?, ?, ?, ?)',
[task.text, task.start_date, task.end_date, task.progress, task.parent],
);

sendResponse(res, 'inserted', result.insertId);
} catch (error) {
sendResponse(res, 'error', null, error);
}
});

// PUT /save/task/:id - Update an existing task
app.put('/save/task/:id', async (req, res) => {
try {
const taskId = req.params.id;
const task = getTask(req.body);

await pool.query(
'UPDATE gantt_tasks SET text = ?, start_date = ?, end_date = ?, progress = ?, parent = ? WHERE id = ?',
[task.text, task.start_date, task.end_date, task.progress, task.parent, taskId],
);

sendResponse(res, 'updated');
} catch (error) {
sendResponse(res, 'error', null, error);
}
});

// DELETE /save/task/:id - Delete a task
app.delete('/save/task/:id', async (req, res) => {
try {
const taskId = req.params.id;
await pool.query('DELETE FROM gantt_tasks WHERE id = ?', [taskId]);
sendResponse(res, 'deleted');
} catch (error) {
sendResponse(res, 'error', null, error);
}
});

Add handlers for link (dependency) operations:

// POST /save/link - Create new link
app.post('/save/link', async (req, res) => {
try {
const link = getLink(req.body);

const [result] = await pool.query('INSERT INTO gantt_links (source, target, type) VALUES (?, ?, ?)', [
link.source,
link.target,
link.type,
]);

sendResponse(res, 'inserted', result.insertId);
} catch (error) {
sendResponse(res, 'error', null, error);
}
});

// PUT /save/link/:id - Update existing link
app.put('/save/link/:id', async (req, res) => {
try {
const linkId = req.params.id;
const link = getLink(req.body);

await pool.query('UPDATE gantt_links SET source = ?, target = ?, type = ? WHERE id = ?', [
link.source,
link.target,
link.type,
linkId,
]);

sendResponse(res, 'updated');
} catch (error) {
sendResponse(res, 'error', null, error);
}
});

// DELETE /save/link/:id - Delete link
app.delete('/save/link/:id', async (req, res) => {
try {
const linkId = req.params.id;
await pool.query('DELETE FROM gantt_links WHERE id = ?', [linkId]);
sendResponse(res, 'deleted');
} catch (error) {
sendResponse(res, 'error', null, error);
}
});

Add Helper Functions

Add utility functions to process data and send responses:

// Helper: Parse task data from request
function getTask(data) {
return {
text: data.text,
start_date: data.start_date,
end_date: data.end_date,
progress: parseFloat(data.progress) || 0,
parent: data.parent || 0,
};
}

// Helper: Parse link data from request
function getLink(data) {
return {
source: data.source,
target: data.target,
type: data.type,
};
}

// Helper: Send response to DataProcessor
function sendResponse(res, action, tid = null, error = null) {
if (error) {
console.error('Error:', error);
return res.status(500).json({ action: 'error', message: error.message });
}

const result = { action };
if (tid !== null) result.tid = tid;
res.json(result);
}

Step 3: Frontend Migration

Remove DevExpress Components and Services

Delete CustomStore service file (src/services/dataService.ts) - DHTMLX React Gantt doesn't use CustomStore

Remove DevExpress CSS links from index.html

If you added DevExpress CSS links in your index.html, remove them:

<!-- Remove these lines -->
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/25.2.4/css/dx.fluent.blue.light.css" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/25.2.4/css/dx-gantt.min.css" />

DHTMLX React Gantt includes its own styles, which are imported directly in the component:

import '@dhtmlx/trial-react-gantt/dist/react-gantt.css';

Update Vite Configuration

Update your vite.config.ts to proxy API requests to the backend server. This is important for development mode:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

const PORT = process.env.PORT || 1337;

export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/load': {
target: `http://localhost:${PORT}`,
changeOrigin: true,
},
'/save': {
target: `http://localhost:${PORT}`,
changeOrigin: true,
},
},
},
});

Update package.json

Make sure your package.json has the correct dependencies:

"dependencies": {
"@dhtmlx/trial-react-gantt": "^9.1.4",
"body-parser": "^2.2.2",
"cors": "^2.8.6",
"date-format-lite": "^17.7.0",
"dotenv": "^17.2.4",
"express": "^5.2.1",
"mysql2": "^3.16.3",
"nodemon": "^3.1.11",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.48.0",
"vite": "^7.3.1"
}

Update src/App.tsx

Replace your DevExpress Gantt component in src/App.tsx with DHTMLX React Gantt:

import { useCallback, useMemo, useRef } from 'react';
import ReactGantt, { type GanttConfig, type Link, type ReactGanttRef, type Task } from '@dhtmlx/trial-react-gantt';
import '@dhtmlx/trial-react-gantt/dist/react-gantt.css';
import './App.css';

function App() {
const ganttRef = useRef<ReactGanttRef>(null);

const config: GanttConfig = useMemo(
() => ({
date_format: '%Y-%m-%d %H:%i:%s',
scales: [
{ unit: 'month', step: 1, format: '%F %Y' },
{ unit: 'week', step: 1, format: 'Week #%W' },
],
open_tree_initially: true,
}),
[]
);

const save = useCallback(
async (entity: string, action: 'update' | 'create' | 'delete', item: Task | Link, id: string | number) => {
switch (action) {
case 'create':
return await fetch(`/save/${entity}`, {
method: 'POST',
body: JSON.stringify(item),
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => response.json())
.then((result) => ({ id: result.tid }));

case 'update':
await fetch(`/save/${entity}/${id}`, {
method: 'PUT',
body: JSON.stringify(item),
headers: {
'Content-Type': 'application/json',
},
});
break;

case 'delete':
await fetch(`/save/${entity}/${id}`, {
method: 'DELETE',
});
break;

default:
throw new Error(`Invalid action: ${action}`);
}
},
[]
);

return (
<ReactGantt
ref={ganttRef}
data={{
load: '/load',
save,
}}
config={config}
/>
);
}

export default App;

Running the Application

For development mode, you need to run two processes:

Terminal 1 - Backend (Express):

npm run server

This starts the API server on http://localhost:1337 (or your configured PORT from .env)

You should see:

Server is running on port 1337

Terminal 2 - Frontend (Vite):

npm run dev

This starts the Vite dev server on http://localhost:5173. Open your browser and navigate to http://localhost:5173. Vite will proxy API requests to the Express backend automatically.

You should see the DHTMLX Gantt chart with your data loaded from the database:

Gantt with data loaded

Explore DHTMLX Gantt Features

Need help?
Got a question about the documentation? Reach out to our technical support team for help and guidance. For custom component solutions, visit the Services page.