Skip to content

Use git endpoints instead of REST APIs for data storage in single-page apps

pattern

Single-page HTML/JS apps with database backends need REST API endpoints for data sync, adding unnecessary complexity

gitapideploymentdata-storagesingle-page-appgolang
31 views

Problem

When you build a lightweight single-page HTML/JS app -- the kind that displays data in a dashboard or feeds content to another app -- you typically need a database and a set of REST API endpoints for CRUD operations. This means designing schemas, writing route handlers, managing migrations, and handling serialization. For simple apps that just need to store and retrieve structured data, this is a lot of infrastructure for very little value. It also makes it harder for AI agents and scripts to interact with your data, since they need to understand your custom API.

Solution

Replace the REST API with a git endpoint served directly from your application server. Store your data as flat files (JSON, CSV, Markdown) in a git repository. Clients and AI agents interact with the data using standard git clone, git pull, and git push commands, authenticated via HTTP basic auth.

Minimal Go server with git HTTP backend:

package main

import (
	"log"
	"net/http"
	"os/exec"
)

func main() {
	// Serve the git repository over HTTP with authentication
	http.HandleFunc("/data.git/", basicAuth(gitHTTPHandler, "admin", "secret"))

	// Serve the single-page app
	http.Handle("/", http.FileServer(http.Dir("./static")))

	log.Println("Serving on :8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func gitHTTPHandler(w http.ResponseWriter, r *http.Request) {
	cmd := exec.Command("git", "http-backend")
	cmd.Env = append(cmd.Environ(),
		"GIT_PROJECT_ROOT=/srv/data",
		"GIT_HTTP_EXPORT_ALL=1",
		"PATH_INFO="+r.URL.Path,
		"REMOTE_USER=admin",
		"REQUEST_METHOD="+r.Method,
	)
	cmd.Stdin = r.Body
	output, err := cmd.Output()
	if err != nil {
		http.Error(w, "git error", 500)
		return
	}
	w.Write(output)
}

func basicAuth(next http.HandlerFunc, user, pass string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		u, p, ok := r.BasicAuth()
		if !ok || u != user || p != pass {
			w.Header().Set("WWW-Authenticate", `Basic realm="data"`)
			http.Error(w, "Unauthorized", 401)
			return
		}
		next(w, r)
	}
}

Initialize the data repository:

mkdir -p /srv/data/data.git
cd /srv/data/data.git
git init --bare
git config http.receivepack true

AI agents and scripts interact with standard git:

# Clone the data
git clone http://admin:secret@localhost:8080/data.git

# Update data
echo '{"users": 42, "active": true}' > data/stats.json
cd data && git add . && git commit -m "update stats" && git push

The single-page app fetches data from the repo:

// Fetch raw file content directly
const stats = await fetch('/data/stats.json').then(r => r.json());

Why It Works

Git is a content-addressable filesystem with built-in versioning, authentication (HTTP basic auth), conflict resolution (merge), and a universally-understood CLI. Every developer and AI agent already knows how to use it. By exposing a git endpoint instead of a REST API, you eliminate route handlers, serialization logic, and database migrations. You get version history for free, rollback is just git revert, and backups are just git clone. Go servers cross-compile to a single binary for any platform, making deployment trivial.

Context

  • This pattern works best for apps with low write frequency -- dashboards, config stores, content feeds, not high-throughput transactional systems
  • Go is ideal for the server because it cross-compiles to a single static binary with zero runtime dependencies
  • Use HTTP basic auth over HTTPS in production -- never expose git endpoints without authentication
  • AI coding agents can natively interact with git, making this pattern particularly useful for agent-driven data pipelines
  • For larger datasets, consider git-lfs or keep files under 10MB to avoid repository bloat
  • The git history doubles as an audit log of every data change
About this share
Contributormblode
Repositorymblode/shares
CreatedFeb 10, 2026
View on GitHub