Implementing Version Control in a Full-Stack App with Node, React, and PostgreSQL

Souvik Majumder
4 min readNov 14, 2024

--

In today’s world, applications must be flexible enough to allow users to create, edit, and sometimes, revert content to a previous state. Think of collaborative writing apps, product management platforms, or project documentation tools where users need the ability to “roll back” to earlier versions. In this article, we’ll walk through building a version control system using Node.js, React, and PostgreSQL, using a document editor as a real-world use case.

Why Add Version Control?

Imagine a team collaborating on project documentation. Team members frequently edit and update content, but occasionally, a critical piece of information gets deleted, or they want to revert to an earlier draft. Without version control, restoring that information can be tedious or even impossible. Adding version control allows users to:

  • Save multiple versions of their work,
  • View and compare changes over time, and
  • Restore earlier versions easily.

Overview of Our Application Stack

For this guide, we’ll use:

  • Node.js for backend API development,
  • React to create a simple frontend interface, and
  • PostgreSQL for persistent data storage and version tracking.

Let’s dive in with a clear example.

Step 1: Set Up Database Tables for Version Control

In this example, our application allows users to create and edit documents. We’ll set up two tables in PostgreSQL:

  1. A documents table to store the current version of each document.
  2. A document_versions table to track historical versions.
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT,
user_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE document_versions (
version_id SERIAL PRIMARY KEY,
document_id INT REFERENCES documents(id),
title TEXT,
content TEXT,
version_number INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
user_id INT
);

Here, each document’s current version is stored in the documents table, while previous versions go into document_versions, with an increasing version_number for easy tracking.

Step 2: Backend API Routes for Version Control

Let’s create API routes for saving versions, listing all previous versions, and rolling back to a chosen version.

Route 1: Save a New Version

Whenever a user saves changes, we first copy the current document into document_versions, then update the main documents table.

// POST /api/documents/:id/version
const saveVersion = async (req, res) => {
const { id } = req.params;
const document = await db.query('SELECT * FROM documents WHERE id = $1', [id]);

if (document.rows.length > 0) {
const { title, content, user_id } = document.rows[0];

// Save current version
await db.query(
`INSERT INTO document_versions (document_id, title, content, version_number, user_id)
VALUES ($1, $2, $3, (SELECT COALESCE(MAX(version_number), 0) + 1 FROM document_versions WHERE document_id = $1), $4)`,
[id, title, content, user_id]
);

// Update document with new content
const { title: newTitle, content: newContent } = req.body;
await db.query(
'UPDATE documents SET title = $1, content = $2, updated_at = NOW() WHERE id = $3',
[newTitle, newContent, id]
);

res.status(201).send('Version saved successfully');
} else {
res.status(404).send('Document not found');
}
};

Route 2: List All Versions

This route will retrieve all versions of a document, allowing users to view the document’s history.

// GET /api/documents/:id/versions
const listVersions = async (req, res) => {
const { id } = req.params;
const versions = await db.query(
'SELECT * FROM document_versions WHERE document_id = $1 ORDER BY version_number DESC',
[id]
);
res.json(versions.rows);
};

Route 3: Roll Back to a Previous Version

When a user chooses a version to roll back, we replace the document’s current content with the selected version’s content.

// POST /api/documents/:id/rollback/:versionId
const rollbackToVersion = async (req, res) => {
const { id, versionId } = req.params;
const version = await db.query(
'SELECT title, content FROM document_versions WHERE version_id = $1 AND document_id = $2',
[versionId, id]
);

if (version.rows.length > 0) {
const { title, content } = version.rows[0];
await db.query(
'UPDATE documents SET title = $1, content = $2, updated_at = NOW() WHERE id = $3',
[title, content, id]
);
res.status(200).send('Document rolled back successfully');
} else {
res.status(404).send('Version not found');
}
};

Step 3: Frontend Implementation in React with ES6

In React, we’ll build a simple UI to display saved versions, view details, and roll back if needed.

Version List Component

import React, { useState, useEffect } from 'react';

const VersionList = ({ documentId, onRollback }) => {
const [versions, setVersions] = useState([]);

useEffect(() => {
fetch(`/api/documents/${documentId}/versions`)
.then((res) => res.json())
.then((data) => setVersions(data));
}, [documentId]);

return (
<div>
<h3>Document Versions</h3>
{versions.map((version) => (
<div key={version.version_id}>
<p>Version {version.version_number}</p>
<button onClick={() => onRollback(version.version_id)}>Roll Back</button>
</div>
))}
</div>
);
};

export default VersionList;

Rollback Handler

In the parent component, define a function to handle rollbacks.

const handleRollback = async (versionId) => {
await fetch(`/api/documents/${documentId}/rollback/${versionId}`, {
method: 'POST',
});
alert(`Rolled back to version ${versionId}`);
};

Step 4: Enhancements for a Better User Experience

To make the version control feature more intuitive, consider adding:

  • Version Previews: Show previews before rolling back.
  • Comparisons: Display differences between versions.
  • Autosave: Automatically save versions at intervals or after significant changes.

Conclusion

Adding a version control feature can significantly improve the user experience by letting them track changes and restore previous work. With the flexibility of Node, React, and PostgreSQL, this functionality can be implemented efficiently, empowering users to confidently manage their content history.

By following this guide, you’ll be well-equipped to add version control to any application, making it valuable for users across industries — from collaborative writing tools to project documentation platforms.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Souvik Majumder
Souvik Majumder

Written by Souvik Majumder

Full Stack Developer | Machine Learning | AI | NLP | AWS | SAP

Responses (1)

Write a response