Introduction

Strux is a Git-native, JSON-based Content Management System that turns your file system into a database and Git into your database engine. Instead of PostgreSQL or MongoDB, all content lives as plain JSON files versioned by Git.

Content-as-Code: Everything — schemas, content, config — is a file. Version it, branch it, merge it, diff it. Just like your code.

Why Strux?

  • Zero database infrastructure — no hosting, no backups, no migrations
  • Native Git versioning — full history, author attribution, branch-based staging
  • Perfect CI/CD integration — frontends read from the file system at build time
  • Developer-first — JSON schemas, REST API, TypeScript throughout

Installation

Quick Start (Recommended)

npx create-strux-app my-project
cd my-project
pnpm install
pnpm dev

Manual Setup

Clone the repository and install dependencies manually:

git clone https://github.com/wahidzzz/strux-cms.git my-project
cd my-project
pnpm install
pnpm build
pnpm dev

Requirements

DependencyVersion
Node.js≥ 20.0.0
Git≥ 2.30
pnpm (recommended)≥ 8.0

Project Structure

Strux is organized as a Turborepo monorepo with three packages:

my-project/
├── packages/
│   ├── core/          # Core engines (FileEngine, GitEngine, etc.)
│   ├── api/           # REST API layer
│   └── admin/         # Next.js admin interface
├── content/           # Content storage (JSON files)
│   └── api/           # Organized by content type
├── schema/            # Content type definitions
│   ├── article.schema.json
│   ├── category.schema.json
│   └── profile.schema.json
├── uploads/           # Media file storage
├── .cms/              # System files (RBAC, users, index)
├── package.json       # Root workspace config
└── turbo.json         # Build pipeline

Package Overview

PackagePurposeKey Tech
@cms/coreBusiness logic, data enginesTypeScript, AJV, Chokidar
@cms/apiREST API endpointsExpress, JWT, bcrypt
@cms/adminAdmin panel UINext.js 14, Tailwind, Radix UI

Collection Types

Collection types represent content that can have multiple entries — like blog posts, products, or users. Each collection type is defined by a JSON Schema file in the schema/ directory.

Defining a Collection Type

{
  "displayName": "Article",
  "kind": "collectionType",
  "singularName": "article",
  "pluralName": "articles",
  "description": "Blog posts and news articles.",
  "apiId": "article",
  "attributes": {
    "title": {
      "type": "string",
      "required": true
    },
    "slug": {
      "type": "uid",
      "targetField": "title",
      "required": true
    },
    "content": {
      "type": "richtext",
      "required": true
    },
    "is_featured": {
      "type": "boolean",
      "required": false
    }
  }
}

Supported Field Types

TypeDescriptionExample
stringShort textTitles, names
textLong textSummaries, excerpts
richtextRich text (HTML)Article body
numberInteger or floatPrice, count
booleanTrue/falsePublished, featured
dateDate stringPublished date
jsonArbitrary JSONMetadata, tags
uidUnique identifier/slugURL slug
mediaFile referenceImages, documents
relationContent relationshipAuthor → Articles
componentReusable componentSEO meta, address
dynamiczoneFlexible block areaPage sections

Single Types

Single types represent content with only one entry — like global settings, a homepage, or site metadata.

{
  "displayName": "Global Settings",
  "kind": "singleType",
  "singularName": "global-settings",
  "attributes": {
    "site_name": { "type": "string", "required": true },
    "tagline": { "type": "string" },
    "logo": { "type": "media" },
    "social_links": { "type": "json" }
  }
}

Note: Single types always have exactly one entry. The admin panel shows an edit form instead of a list view.

Relationships

Strux supports four types of relationships between content types:

one-to-one

A single entry relates to exactly one entry in another type.

// In user.schema.json
"profile": {
  "type": "relation",
  "relation": {
    "target": "profile",
    "relation": "oneToOne"
  }
}

one-to-many

One entry can relate to many entries in another type.

// In author.schema.json
"articles": {
  "type": "relation",
  "relation": {
    "target": "article",
    "relation": "oneToMany"
  }
}

many-to-one

Many entries relate to one entry (the inverse of one-to-many).

// In article.schema.json
"category": {
  "type": "relation",
  "relation": {
    "target": "category",
    "relation": "manyToOne"
  }
}

many-to-many

Multiple entries can relate to multiple entries in another type.

// In article.schema.json
"tags": {
  "type": "relation",
  "relation": {
    "target": "tag",
    "relation": "manyToMany"
  }
}

Population

Relationships are resolved at query time using the populate query parameter:

GET /api/articles?populate=author,category
GET /api/articles?populate=*  // Populate all relations

Dynamic Zones

Dynamic zones let editors compose pages from reusable content blocks (components). Each block has its own schema and can be added, removed, or reordered by editors.

Defining Components

Components live in the schema/components/ directory:

// schema/components/hero.json
{
  "displayName": "Hero",
  "attributes": {
    "heading": { "type": "string", "required": true },
    "subheading": { "type": "text" },
    "background_image": { "type": "media" },
    "cta_text": { "type": "string" },
    "cta_url": { "type": "string" }
  }
}

Using Dynamic Zones in Schemas

// In page.schema.json
"body": {
  "type": "dynamiczone",
  "components": ["hero", "feature-grid", "testimonials", "cta"]
}

Content Structure

Dynamic zone content is stored as an array of typed blocks:

{
  "body": [
    {
      "__component": "hero",
      "heading": "Welcome to Our Site",
      "subheading": "We build amazing things"
    },
    {
      "__component": "feature-grid",
      "features": [...]
    }
  ]
}

Content Modeling Guide

Effective content modeling is the foundation of a good CMS experience. Here are best practices for Strux:

1. Start with the API Consumer

Think about how your frontend will consume the data. Design schemas that match your component structure.

2. Use Components for Reusable Structures

If multiple content types share the same fields (e.g., SEO metadata, address blocks), extract them into components.

3. Choose the Right Relationship Type

ScenarioRelationship
User has one profileone-to-one
Author writes many articlesone-to-many
Article belongs to one categorymany-to-one
Article has many tags, tag has many articlesmany-to-many

4. Use Dynamic Zones for Flexible Pages

For landing pages or marketing pages where the layout varies, use dynamic zones instead of fixed fields.

API Usage

Strux provides a REST API for full CRUD operations on your content.

Base URL

http://localhost:3000/api

Endpoints

MethodEndpointDescription
GET/api/:pluralNameList all entries
GET/api/:pluralName/:idGet single entry
POST/api/:pluralNameCreate entry
PUT/api/:pluralName/:idUpdate entry
DELETE/api/:pluralName/:idDelete entry

Query Parameters

// Pagination
GET /api/articles?page=1&pageSize=10

// Sorting
GET /api/articles?sort=createdAt:desc

// Filtering
GET /api/articles?filters[is_featured]=true

// Population
GET /api/articles?populate=author,category

// Search
GET /api/articles?search=hello

Authentication

// Register
POST /api/auth/register
{ "email": "user@example.com", "password": "secure123", "username": "user1" }

// Login
POST /api/auth/login
{ "email": "user@example.com", "password": "secure123" }

// Returns: { "token": "jwt-token-here", "user": { ... } }

// Use token in subsequent requests
Authorization: Bearer jwt-token-here

Deployment Guide

Strux can be deployed anywhere Node.js runs. Here are the most common deployment patterns:

Self-Hosted (VPS / VM)

# Build for production
pnpm build

# Start the server
NODE_ENV=production pnpm start

# Or use PM2
pm2 start npm --name "strux" -- start

Docker

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install -g pnpm && pnpm install && pnpm build
EXPOSE 3000
CMD ["pnpm", "start"]

Environment Variables

VariableDefaultDescription
PORT3000Server port
JWT_SECRETSecret for JWT signing (required)
NODE_ENVdevelopmentEnvironment mode
CONTENT_DIR./contentContent storage path
SCHEMA_DIR./schemaSchema definitions path

Important: Always set a strong JWT_SECRET in production. Never commit it to your repository.