Files
restxlsx/README.md
T
2026-06-10 13:01:57 +02:00

10 KiB

restxlsx

A RESTful web service that exposes Microsoft Excel (.xlsx / .xlsm) tables as a live API. Supports REST CRUD, GraphQL, SQL queries, and DDL operations — all backed by an xlsx file as the data store.


Quick Start

# Use the sample data
./restxlsx

# Or point at your own file
./restxlsx --file data/sample.xlsx

# Open http://localhost:3000/health

Configuration

All settings can be provided via CLI flags or environment variables. Flags take precedence over env vars.

Flag Env Var Default Description
--file RESTXLSX_FILE data/sample.xlsx Path to the xlsx/xlsm data file
--host RESTXLSX_HOST localhost Interface to bind to (0.0.0.0 for all)
--port RESTXLSX_PORT 3000 TCP port
--debug RESTXLSX_DEBUG false Enable OpenAPI spec, Swagger UI, GraphiQL
--quiet RESTXLSX_CONSOLE=quiet false Suppress all stdout output
--log-reads RESTXLSX_LOG_READS true Log GET/HEAD requests (false to skip)
--log-file RESTXLSX_LOG_FILE "" Optional path to append logs to file
--auth RESTXLSX_AUTH writer Fallback auth role (see Authorization below)

Examples:

# Listen on all interfaces, port 8080, reader-only
./restxlsx --host 0.0.0.0 --port 8080 --auth reader

# Using env vars
RESTXLSX_FILE=./myfile.xlsx RESTXLSX_DEBUG=true ./restxlsx

# Quiet mode, no read logging, log to file
./restxlsx --quiet --log-reads=false --log-file /var/log/restxlsx.log

Endpoints

Health

GET /health

REST CRUD — GET/POST/PUT/DELETE /api/{table}[/{id}]

Method Path Description Auth
GET /api/tables List all discovered table names reader+
GET /api/{table} List all records in a table reader+
GET /api/{table}/{id} Get a single record by its first-column ID reader+
POST /api/{table} Create a new record writer+
PUT /api/{table}/{id} Update an existing record writer+
DELETE /api/{table}/{id} Delete a record writer+

The first column of each table is treated as the unique ID column. If omitted on create, an auto-increment value is assigned.

GraphQL — POST /graphql

Query and mutate data via GraphQL. Dynamic schema is generated from the tables at startup.

# Query
{ list_Employees { ID Name Salary } }

# Get single
{ get_Employees(id: "1") { Name Department } }

# Mutate
mutation { create_Employees(ID: "99", Name: "New", Department: "QA", Salary: "70000") { ID } }

When --debug is enabled, GET /graphql serves the GraphiQL IDE.

SQL — POST /api/sql

Execute SQL statements against the data. Supports SELECT, INSERT, UPDATE, DELETE with optional WHERE.

// SELECT
{"query": "SELECT Name, Salary FROM Employees WHERE ID = '1'"}

// SELECT all
{"query": "SELECT * FROM Employees"}

// INSERT
{"query": "INSERT INTO Employees (ID, Name, Department) VALUES ('42', 'Alice', 'Eng')"}

// UPDATE
{"query": "UPDATE Employees SET Salary = '100000' WHERE ID = '42'"}

// DELETE
{"query": "DELETE FROM Employees WHERE ID = '42'"}

The SQL dialect is intentionally minimal — only = comparisons in WHERE, string/number values, and single statements per request. This maps directly to the in-memory table operations.

DDL — POST/DELETE /api/ddl/*

Schema management (admin only).

Method Path Description
POST /api/ddl/sheets Create a new sheet (empty, with a default ID column)
DELETE /api/ddl/sheets/{sheet} Delete a sheet and all its data
POST /api/ddl/tables Define columns on a sheet (effectively creates a table schema)
DELETE /api/ddl/tables/{table} Remove all data from a table (keeps the sheet)

Create sheet:

POST /api/ddl/sheets
{"name": "MyNewSheet"}

Define table on a sheet:

POST /api/ddl/tables
{"sheet": "MyNewSheet", "name": "Projects", "columns": ["ID", "Name", "Budget"]}

OpenAPI / Swagger (debug mode)

When --debug is set:

Endpoint Description
GET /openapi.json Auto-generated OpenAPI 3.0.3 specification
GET /swagger Swagger UI interactive documentation

Authorization

Authorization is implemented as a role-based system with three levels. The --auth flag (or RESTXLSX_AUTH env var) sets the fallback role that applies to all requests. This is the effective authorization until OIDC/OAuth2 integration is added (see below).

Role hierarchy

Role Read Write (POST/PUT/DELETE) DDL (create/drop)
reader
writer (default)
admin

For unauthenticated / open access

To run the service with no authentication barrier (everyone can read and write):

./restxlsx --auth writer

This is the default — all callers are treated as writer. They can read and mutate data but cannot modify the schema. If you also need DDL access:

./restxlsx --auth admin

For read-only public access:

./restxlsx --auth reader

Future: OIDC / OAuth2 integration

The auth architecture (internal/auth/) is designed to be upgraded to token-based claims. The current --auth fallback role will become the default for unauthenticated requests.

Planned integration path:

  1. The auth.Middleware will first inspect the Authorization header for a Bearer JWT.
  2. If a valid token is present, the roles claim (or a custom claim mapping) is extracted and the effective role becomes the token's role.
  3. If no token is present (or the token is invalid/expired), the fallback --auth role applies.
  4. A future --oidc-issuer, --oidc-client-id, and --oidc-claim-roles config will wire the validation.

Middleware structure (ready for extension):

internal/auth/
├── auth.go          # Role type, helpers, RequireWrite/RequireDDL middlewares
├── oidc.go          # (future) OIDC token validation, claim extraction
└── middleware.go     # (future) Combined middleware: try OIDC, fallback to role

The auth.Middleware currently injects a static role into the request context. To add OIDC, you would:

  1. Parse the Bearer token in the middleware
  2. Validate against the OIDC issuer's JWKS
  3. Map the roles claim to a Role
  4. Store it in context (same roleKey{})
  5. All downstream RequireWrite / RequireDDL checks work unchanged

Data Persistence & Concurrency

The xlsx file on disk is the persistent store. The workflow:

  1. On startup, the xlsx file is read into memory
  2. All operations read from the in-memory snapshot (fast, no file I/O on reads)
  3. Every write operation mutates memory and immediately flushes to the xlsx file
  4. The file is not held open between flushes — other applications (Excel, etc.) can freely edit the file while the service is running
  5. During a flush, the service merges any new sheets that were added externally, so external additions are preserved
  6. If the file cannot be opened for writing (e.g., locked by another process), the operation fails with 503 Service Unavailable
  7. In-memory data is protected by a sync.Mutex, so concurrent HTTP requests are serialized at the engine level — safe for concurrent access

Note: Updates to existing rows from an external editor will be overwritten by the next flush from this service. For collaborative editing, keep the xlsx as the primary source and use this service as a proxy.


Docker

# Build
docker build -t restxlsx .

# Run (default sample data included)
docker run -p 3000:3000 restxlsx

# With your own xlsx file
docker run -p 3000:3000 -v /path/to/your.xlsx:/data/sample.xlsx restxlsx

The Dockerfile uses a multi-stage build resulting in a ~15 MB scratch image.


Development

Prerequisites

  • Go 1.24+
  • Python 3.10+ (only needed for sample data generation)

Regenerate sample data

python3 -m venv /tmp/venv && /tmp/venv/bin/pip install openpyxl
/tmp/venv/bin/python scripts/gen_data.py

Project layout

restxlsx/
├── main.go                     # Entry point, router, graceful shutdown
├── Dockerfile                  # Multi-stage scratch build
├── internal/
│   ├── config/config.go        # CLI flags + env var configuration
│   ├── logger/logger.go        # Structured logging via slog
│   ├── excel/excel.go          # XLSX engine: read, write, flush, DDL
│   ├── auth/auth.go            # Role-based authorization middleware
│   ├── graphql/graphql.go      # Dynamic GraphQL schema & handler
│   └── api/
│       ├── rest.go             # REST CRUD handlers
│       ├── sql.go              # SQL endpoint + minimal SQL parser
│       ├── ddl.go              # DDL handlers (create/drop sheets/tables)
│       ├── openapi.go          # Dynamic OpenAPI spec generation
│       ├── middlewares.go      # JSON content-type middleware
│       └── swagger-ui.html     # Embedded Swagger UI
├── data/sample.xlsx            # Sample workbook (3 tables)
├── scripts/gen_data.py         # Python script to generate sample data
└── tasks.md                    # Architecture decisions & progress log

Building from Source

Cross-platform builds

The scripts/build.sh script cross-compiles binaries for Linux, Windows, and macOS:

chmod +x scripts/build.sh
./scripts/build.sh

Binaries are placed in the dist/ directory:

File Platform
dist/restxlsx-linux-amd64 Linux (x86_64)
dist/restxlsx-windows-amd64.exe Windows (x86_64)
dist/restxlsx-darwin-amd64 macOS (Intel)
dist/restxlsx-darwin-arm64 macOS (Apple Silicon)

Manual build

go build -o restxlsx .

Dependency rationale

Library Purpose
github.com/go-chi/chi/v5 Lightweight, idiomatic HTTP router
github.com/xuri/excelize/v2 Full xlsx read/write support
github.com/graphql-go/graphql Dynamic schema generation from data