How-to: a blog
Write posts in a form, publish a static site — the whole loop from one poops.json. Drafts stay a database row; only published posts become pages.
1. Scaffold
mkdir blog && cd blog && npm init -y
npm i -D poops laxative
2. One config
{
"markup": { "in": "src/markup", "out": "dist" },
"septic": {
"db": "data/blog.db",
"auth": { "seed": { "email": "you@example.com", "password": "changeme", "role": "admin" } },
"resources": {
"posts": {
"methods": ["GET", "POST", "PUT", "DELETE"],
"access": { "read": "public", "write": "admin" },
"fields": {
"title": "string required",
"slug": "slug required unique",
"body": "text required",
"excerpt": "text",
"status": "enum(draft,published) = draft",
"published_at": "datetime",
"created": "datetime = now",
"updated": "datetime = now!"
}
}
},
"build": {
"resources": {
"posts": { "into": "src/markup/posts", "slug": "slug", "body": "body", "layout": "post.html", "where": { "status": "published" } }
},
"forms": {
"posts": { "into": "src/markup/_partials", "submitLabel": "Publish" }
}
}
}
}
Three blocks, one file: the schema + API (resources), the static build (build.resources, published only), and the author form (build.forms).
3. A post layout
src/markup/_layouts/post.html renders each generated post. This is poops templating:
<!-- _layouts/post.html -->
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>{{ page.title }}</title></head>
<body>
<article>
<h1>{{ page.title }}</h1>
<time>{{ page.published_at }}</time>
{{ content }}
</article>
<p><a href="/">← all posts</a></p>
</body></html>
An src/markup/index.html lists the posts collection (see poops collections). The generated _partials/posts-form.html is your write/edit form — include it on an admin page.
4. Write, then publish
npx laxative dev # http://localhost:3000
Post to the API (or use the generated form) as the seeded admin:
curl -c jar -X POST localhost:3000/api/_auth/login \
-H 'content-type: application/json' -d '{"email":"you@example.com","password":"changeme"}'
curl -b jar -X POST localhost:3000/api/posts -H 'content-type: application/json' \
-d '{"title":"Hello","slug":"hello","body":"# Hi","status":"published","published_at":"2026-08-05 12:00:00"}'
A draft is just "status":"draft" — it lives in the DB and never reaches the site until you flip it. GET /api/posts/:id as admin returns a prefilled edit form to do exactly that.
5. Publish the static site
npx laxative build # published posts → markup → poops → dist/
npx laxative serve # serve dist/ + the /api (to keep writing) on one origin
Deploy dist/ anywhere — it's plain HTML. Keep laxative serve running where you author; rebuild to publish.
The loop
Write in a form → it's a draft row → flip to published → laxative build → a static page. Content in a database, a static site out, an author form in between — one config.
The backend half alone — schema, the where filter, the generated form, no app around it — is septic's version of this how-to.