riley-cms-api

Crates.ioriley-cms-api
lib.rsriley-cms-api
version0.1.0
created_at2026-01-24 00:18:25.113226+00
updated_at2026-01-24 00:18:25.113226+00
descriptionHTTP API server for riley_cms - Axum-based REST endpoints
homepage
repositoryhttps://github.com/rileyleff/riley_cms
max_upload_size
id2065767
size148,568
Riley Leff (RileyLeff)

documentation

https://docs.rs/riley-cms-api

README

riley_cms

Crates.io Documentation License: MIT

A minimal, self-hosted headless CMS for personal blogs. Rust, no database, no GUI.

Git is your content database. S3/R2 is your asset database. riley_cms is the stateless glue that serves it.

Philosophy

  • Git is the database for content - version controlled, portable, yours forever
  • S3/R2 is the database for assets - cheap, fast, globally distributed
  • The API is stateless glue - easy to deploy, easy to scale
  • Opinionated by design - less config, less bikeshedding
  • Headless - you control the frontend, riley_cms serves JSON

Quick Start

Install

cargo install riley-cms-cli

Or build from source:

git clone https://github.com/rileyleff/riley_cms
cd riley_cms
cargo build --release

Initialize Content

riley_cms init my-blog
cd my-blog

This creates an example content structure:

my-blog/
├── riley_cms.toml
└── content/
    ├── hello-world/
    │   ├── config.toml
    │   └── content.mdx
    └── my-series/
        ├── series.toml
        ├── part-one/
        │   ├── config.toml
        │   └── content.mdx
        └── part-two/
            ├── config.toml
            └── content.mdx

Configure

Edit riley_cms.toml:

[content]
repo_path = "."
content_dir = "content"

[storage]
backend = "s3"
bucket = "my-assets"
region = "auto"
endpoint = "https://xxx.r2.cloudflarestorage.com"
public_url_base = "https://assets.example.com"

[server]
host = "0.0.0.0"
port = 8080
cors_origins = ["https://mysite.com"]

Run

riley_cms serve

Content Structure

Posts live in directories with config.toml + content.mdx:

content/
├── my-post/
│   ├── config.toml
│   └── content.mdx
└── my-series/
    ├── series.toml          # Makes this a series
    ├── getting-started/
    │   ├── config.toml
    │   └── content.mdx
    └── advanced-topics/
        ├── config.toml
        └── content.mdx

Post Config

title = "My Post Title"
subtitle = "Optional subtitle"
preview_text = "A short preview for listings..."
preview_image = "https://assets.example.com/preview.jpg"
tags = ["rust", "programming"]
goes_live_at = 2025-01-15T00:00:00Z  # None = draft, future = scheduled
order = 1  # For series posts

Series Config

title = "My Series"
description = "Learn something cool"
preview_image = "https://assets.example.com/series.jpg"
goes_live_at = 2025-01-15T00:00:00Z

API

Endpoint Description
GET /posts List all live posts
GET /posts/:slug Get a single post with content
GET /posts/:slug/raw Get raw MDX content only
GET /series List all live series
GET /series/:slug Get series with ordered posts
GET /assets List assets in bucket
GET /health Health check
* /git/{*path} Git Smart HTTP (requires Basic Auth)

Query Parameters

  • ?include_drafts=true - Include unpublished posts (requires auth)
  • ?include_scheduled=true - Include future-dated posts (requires auth)
  • ?limit=N - Limit results (default: 50)
  • ?offset=N - Skip results for pagination

Authentication

riley_cms supports two authentication mechanisms:

API Token (Bearer)

For accessing drafts and scheduled content via the API:

curl -H "Authorization: Bearer your-api-token" \
  "http://localhost:8080/posts?include_drafts=true"

Configure in riley_cms.toml:

[auth]
api_token = "env:API_TOKEN"  # Read from environment variable
# or
api_token = "your-literal-token"

Git Token (Basic Auth)

For pushing content via Git over HTTP:

git remote add origin http://git:your-token@localhost:8080/git
git push origin main

Configure in riley_cms.toml:

[auth]
git_token = "env:GIT_AUTH_TOKEN"

Git Server

riley_cms can serve your content repository over HTTP, allowing you to push content updates directly to the server.

Setup

  1. Initialize a bare git repo in your content directory (or use an existing one)
  2. Configure the git_token in your config
  3. Add the remote to your local clone:
# On your local machine
git remote add cms http://git:your-token@your-server:8080/git
git push cms main

Endpoints

Endpoint Description
GET /git/{*path} Git read operations (fetch/clone)
POST /git/{*path} Git write operations (push)

After a successful push, riley_cms automatically:

  1. Refreshes the content cache
  2. Fires any configured webhooks

Response Example

{
  "posts": [
    {
      "slug": "my-post",
      "title": "My Post Title",
      "subtitle": null,
      "preview_text": "A short preview...",
      "preview_image": "https://assets.example.com/preview.jpg",
      "tags": ["rust"],
      "series_slug": null,
      "goes_live_at": "2025-01-15T00:00:00Z"
    }
  ],
  "total": 42,
  "limit": 50,
  "offset": 0
}

CLI

riley_cms serve              # Run the HTTP API
riley_cms init <path>        # Initialize content structure
riley_cms upload <file>      # Upload asset to S3/R2
riley_cms ls posts           # List posts
riley_cms ls series          # List series
riley_cms ls assets          # List assets
riley_cms validate           # Check content for errors

Crates

Crate Description
riley-cms-core Core library - embed in your own apps
riley-cms-api Axum HTTP server
riley-cms-cli CLI binary

Using riley-cms-core as a Library

use riley_cms_core::{RileyCms, resolve_config, ListOptions};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = resolve_config(None)?;
    let riley_cms = RileyCms::from_config(config).await?;

    let posts = riley_cms.list_posts(&ListOptions::default()).await?;
    for post in posts.items {
        println!("{}: {}", post.slug, post.title);
    }

    Ok(())
}

Deployment

Docker

FROM rust:1.88 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y git ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/riley_cms /usr/local/bin/
VOLUME /data
EXPOSE 8080
CMD ["riley_cms", "serve"]

Docker Compose

services:
  riley_cms:
    build: .
    volumes:
      - riley_cms_data:/data
      - ./riley_cms.toml:/etc/riley_cms/config.toml:ro
    environment:
      - GIT_AUTH_TOKEN=${GIT_AUTH_TOKEN}
      - API_TOKEN=${API_TOKEN}
      - AWS_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
    ports:
      - "8080:8080"
    restart: unless-stopped

volumes:
  riley_cms_data:

Configuration

Config is loaded from (first match wins):

  1. --config <path> flag
  2. RILEY_CMS_CONFIG env var
  3. riley_cms.toml in current directory
  4. Walk up ancestors for riley_cms.toml
  5. ~/.config/riley_cms/config.toml
  6. /etc/riley_cms/config.toml

See riley_cms.example.toml for all options.

License

MIT

Commit count: 22

cargo fmt