Clone the core of diagram-design in 45 minutes
Welcome to another official UVF IT guide! In this quick build you’ll create a minimal yet functional diagram generator that renders SVG directly from a tiny JSON description, inspired by the open‑source *diagram‑design* project.
We’ll use plain HTML, a tiny Node.js server, and SQLite for persisting diagram definitions – all on the free tier and perfect for a first‑time developer.
Create a new folder for your diagram generator, initialise it as an npm project, and pull in the two runtime dependencies we need: Express for the web server and better‑sqlite3 for a lightweight, synchronous SQLite wrapper. This gives us a clean starting point and a place to put our code.
We’ll also add a minimal entry file (index.js) that starts the Express server on port 3000. The file uses native ES modules, so the type field in package.json is set to module. This scaffold is the foundation for the rest of the guide.
mkdir diagram-clone
cd diagram-clone
npm init -y
# enable ES modules
npm pkg set type=module
npm install express better-sqlite3
# create a basic server entry point
cat > index.js <<'EOF'
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
app.get('/api/health', (req, res) => res.json({status:'ok'}));
const PORT = process.env.PORT||3000;
app.listen(PORT,()=>console.log(`Server listening on http://localhost:${PORT}`));
EOFA single SQLite table will hold the diagram definitions as JSON strings. This keeps persistence trivial while still letting us query or update later. We expose a small helper module (db.js) that creates the table if it doesn’t exist and provides a function to insert a sample diagram – useful for the first UI test.
The module uses better‑sqlite3’s synchronous API, which is perfect for a tiny prototype and avoids callback/Promise noise. All other code will import this helper to read or write diagram data.
db.js
import Database from 'better-sqlite3';
// Open (or create) a SQLite file in the project root
const db = new Database('diagrams.db');
// Ensure the table exists – id is auto‑increment, json stores the diagram description
const init = db.prepare(`
CREATE TABLE IF NOT EXISTS diagrams (
id INTEGER PRIMARY KEY AUTOINCREMENT,
json TEXT NOT NULL
)
`).run();
/** Insert a sample diagram JSON and return its new id */
export function insertSample() {
const sample = JSON.stringify({
nodes: [{id:'A',x:50,y:50,width:100,height:50,label:'Start'}],
edges: []
});
const stmt = db.prepare('INSERT INTO diagrams (json) VALUES (?)');
const info = stmt.run(sample);
return info.lastInsertRowid;
}
/** Retrieve a diagram by id */
export function getDiagram(id) {
const row = db.prepare('SELECT json FROM diagrams WHERE id = ?').get(id);
return row ? JSON.parse(row.json) : null;
}
export default db;In this step we spin up a tiny Express server that will host our static HTML page and expose a JSON endpoint for diagram data. The server listens on port 3000, serves everything inside the public folder, and returns a hard‑coded diagram when the client asks for /api/diagram/1. Keeping the data inline makes the clone simple while still showing the full request‑response flow.
server.mjs
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = 3000;
// Serve static assets from the public folder
app.use(express.static(path.join(__dirname, 'public')));
// Simple in‑memory diagram store
const diagrams = {
1: {
nodes: [
{id: 'A', x: 50, y: 50, label: 'Start'},
{id: 'B', x: 250, y: 50, label: 'End'}
],
edges: [{from: 'A', to: 'B'}]
}
};
// API endpoint to fetch a diagram by id
app.get('/api/diagram/:id', (req, res) => {
const diagram = diagrams[req.params.id];
if (diagram) res.json(diagram);
else res.status(404).json({error: 'Diagram not found'});
});
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});The browser now loads index.html (provided in the scaffold) and runs this tiny client script. It fetches the diagram JSON from the API, then creates SVG <rect> elements for each node and a <line> for each edge. All coordinates are taken directly from the JSON, so you can see how data drives the visual output without any extra libraries.
public/app.js
// public/app.js – runs in the browser
(async () => {
const resp = await fetch('/api/diagram/1');
if (!resp.ok) return console.error('Failed to load diagram');
const diagram = await resp.json();
const svg = document.getElementById('diagram');
// Render edges first so lines appear beneath boxes
diagram.edges.forEach(e => {
const from = diagram.nodes.find(n => n.id === e.from);
const to = diagram.nodes.find(n => n.id === e.to);
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', from.x + 40);
line.setAttribute('y1', from.y + 20);
line.setAttribute('x2', to.x + 40);
line.setAttribute('y2', to.y + 20);
line.setAttribute('stroke', '#333');
line.setAttribute('stroke-width', '2');
svg.appendChild(line);
});
// Render nodes as rectangles with text labels
diagram.nodes.forEach(n => {
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttribute('x', n.x);
rect.setAttribute('y', n.y);
rect.setAttribute('width', '80');
rect.setAttribute('height', '40');
rect.setAttribute('fill', '#eef');
rect.setAttribute('stroke', '#555');
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', n.x + 40);
text.setAttribute('y', n.y + 25);
text.setAttribute('text-anchor', 'middle');
text.setAttribute('font-family', 'sans-serif');
text.textContent = n.label;
group.appendChild(rect);
group.appendChild(text);
svg.appendChild(group);
});
})();Now everything is wired together. Start the server, open a browser to http://localhost:3000, and you should see two labeled boxes with an arrow between them. From this foundation you can experiment: add more nodes to the diagrams object, switch the endpoint to accept POST requests, or enhance the SVG styling.
node server.mjs| Check | Command / Why it matters |
|---|---|
| Server starts without errors | node server.mjs → console shows ‘Server running at http://localhost:3000’ |
| Browser loads http://localhost:3000 and shows two boxes with an arrow | Open Chrome/Firefox, navigate to the URL and visually confirm the SVG |
| API returns JSON for diagram id 1 | curl http://localhost:3000/api/diagram/1 | jq '.' |
Your minimal diagram generator is live. Remember the real diagram‑design library adds layout engines, motion, and many diagram types – you now have a solid base to explore those features.