---
title: "Example: Simple Blog · RUAL Documentation"
description: "Posts, slugs, tags and a published state."
canonical: https://docs.rual.nl/examples/simple-blog
language: en
---

# Example: Simple Blog

A complete working blog: post model, list and detail pages, publishing flow, and the APIs behind them.

A blog is the smallest complete app: one storage, two pages, one publishing flow. This example shows every piece working together: clone the structure for any content-driven site.

### The Post Document

```
{
  "_meta": {
    "cms": 1782080295147,
    "created": 1782080295,
    "entity": 1,
    "expiry": -1,
    "guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
    "removed": 0,
    "ums": 1782166695813,
    "update_hash": "60e27209fa93063b5605e41605c2722ed428cae0",
    "updated": 1782166695
  },
  "author_guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
  "author_name": "sami b",
  "body": "# Why we rebuilt\n\nEverything starts with...",
  "published_at": 1782166695,
  "slug": "why-we-rebuilt-the-docs",
  "status": "published",
  "tags": [
    "docs",
    "meta"
  ],
  "title": "why we rebuilt the docs"
}
```

One `posts` storage, everything embedded: the author is denormalized (`author_name` stored next to `author_guid`) so list pages never join. The core storage principle from [Storages](https://docs.rual.nl/blueprints/storage).

### Public Pages

- **Index page** (`/blog`, scope `*public`): a `function_search` for `status = published`, sorted by `published_at` descending, limit 20. The list-query shape from [templates](https://docs.rual.nl/block-types/block-templates#api-endpoint). Iterate results into post cards.

- **Post page** (`/blog/post`, `*public`): reads `slug` from the page params, fetches with [`function_search_single_result`](https://docs.rual.nl/block-types/storage/function_search_single_result) (term on `slug` + term on `status = published`: drafts 404 for the public), renders title/body with state elements.

- **Feed**: the same list query behind an API block → JSON for external readers. See [Creating a REST API](https://docs.rual.nl/tutorials/rest-api).

### The Publishing Flow

- An editor form (`*loggedin` + custom `editor` scope) writes drafts with [`function_create_document_from_mutations`](https://docs.rual.nl/block-types/storage/function_create_document_from_mutations). The form template from [Block Templates](https://docs.rual.nl/block-types/block-templates#form-handling).

- The slug is generated once from the title (lowercase, dashes) with a uniqueness check before create.

- Publishing is a status mutation (`draft → published` + `published_at` timestamp) via [`function_update_document_mutations`](https://docs.rual.nl/block-types/storage/function_update_document_mutations).

- Edits create revisions automatically; the list only ever shows `published`.

### Search

Add a search box feeding [`query_bool_simple_query_string_field`](https://docs.rual.nl/block-types/query/query_bool_simple_query_string_field) on `title` + `body` into the same list query: lowercase the fields at write time, per [search optimization](https://docs.rual.nl/blueprints/storage-examples#search-optimization).

### Takeaways to Reuse

- One storage per content type; embed what lists display.

- Status fields drive visibility. Never delete content, transition it.

- Public reads and editor writes are separate pages with separate scopes.

- The same query serves the page, the API, and the search box.

### Next Steps
