Template
1
Fork 0
The blog client as a hand-rolled Dart web SPA — no Flutter, no framework, no CSS library: package:web for the DOM, a hash router, one stylesheet, and dart compile js. Post list, post page with rendered markdown, login, and an editor with a publish toggl https://devai.io/templates/blog-dart
  • Dart 75%
  • CSS 20.3%
  • Dockerfile 3%
  • HTML 1.7%
Find a file Use this template
Leonardo Devai ae72d24ce1 Review and modernize all 42 projects to the updated standard
Pinned 2026 toolchains (Go 1.26, Rust 1.98/edition 2024, Python 3.14 + uv, Node 24,
Zig 0.16, NixOS 26.05), postgres 18 / mongo 8, lockfiles built from, non-root
runtimes, .dockerignore, per-project LICENSE, READMEs with the git.devai.io clone
line, checkout@v7 CI. Security fixes in the legacy Rust APIs (any-password login,
self-assigned admin, hard-coded JWT secret), JWT alg/exp/sub enforcement across the
blog series, safe markdown links in the frontends, and many smaller bugs — every
project was built, run and exercised end to end.

Adds scripts/publish.sh + a CI publish job that splits every folder into its own
repo at git.devai.io/templates/<folder>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01128fhuZbgivaSJvtMf4s1G
2026-09-27 21:10:38 +02:00
.github/workflows Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
web Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
.dockerignore Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
.env.example Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
.gitignore Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
analysis_options.yaml Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
compose.yaml Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
Dockerfile Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
LICENSE Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
nginx.conf.template Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
pubspec.lock Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
pubspec.yaml Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00
README.md Review and modernize all 42 projects to the updated standard 2026-09-27 21:10:38 +02:00

blog-dart

The blog client as a hand-rolled Dart web SPA — no Flutter, no framework, no CSS library: package:web for the DOM, a hash router, one stylesheet, and dart compile js. Post list, post page with rendered markdown, login, and an editor with a publish toggle. It works against any backend of the devai.io blog engine series.

Run

Get it: git clone https://git.devai.io/templates/blog-dart.git

docker compose up --build

Open http://localhost:8081. The app expects a blog API on http://localhost:8080 — start one of the backend siblings first (e.g. docker compose up --build in blog-go-postgres), then create a user, since there is no sign-up page:

curl -X POST localhost:8080/auth/register -H 'Content-Type: application/json' \
  -d '{"email":"me@example.com","password":"secret123"}'

Without Docker (Dart 3.13), check and compile:

dart pub get
dart analyze
dart compile js -O2 -o build/main.dart.js web/main.dart

How it works

The browser only ever talks to one origin. The app calls /api/... and nginx forwards /api/* to API_URL — so the API needs no CORS setup.

  • API_URL (runtime, default http://host.docker.internal:8080, no trailing slash) — where nginx sends /api/*. Set it in .env or the environment; no rebuild needed. On Linux, a host firewall (firewalld, NixOS) may block containers from reaching the host — allow the Docker bridges, or point API_URL at the API directly.
  • API_URL as a compile-time define (default /api) — the base URL baked into main.dart.js: dart compile js -DAPI_URL=https://api.example.com .... Use a full URL only if the app should call an API directly; that API must then send CORS headers.

Everything lives in web/main.dart (~480 lines): a typed fetch wrapper, the Post/PostSummary models, a small DOM helper, the markdown renderer and four views dispatched from the hash route (#/, #/posts/<slug>, #/login, #/write, #/edit/<slug>) — hash routes need no server-side fallback.

Auth: POST /auth/login returns a JWT. It is kept in memory, mirrored to localStorage (blog_token) so a reload stays signed in, and sent as Authorization: Bearer <token>. Log out clears both.

Markdown becomes DOM nodes whose text is set with textContent — nothing is parsed as HTML — and only http(s):, mailto: and relative links become anchors, so a post body cannot inject markup.

The API never returns unpublished posts, so the editor keeps a freshly saved draft open; tick Published and save again to make it public.

API calls used:

POST   /auth/login          {email, password} -> {token}
GET    /posts               -> [{id, title, slug, excerpt, published_at}]
GET    /posts/{slug}        -> full post
POST   /posts        (auth) {title, body} -> post (unpublished)
PUT    /posts/{id}   (auth) {title?, body?, published?} -> post
DELETE /posts/{id}   (auth) -> 204

Layout

web/index.html          shell page: loads styles.css and main.dart.js
web/main.dart           API client, markdown renderer, views, hash router
web/styles.css          plain CSS, light and dark via prefers-color-scheme
nginx.conf.template     /api proxy; API_URL filled in at start-up

Deploy

Push to your own GitHub repo and the shipped workflow (.github/workflows/ci.yml) tests the compose stack, publishes the image to GHCR, and — once you set the DEPLOY_HOST / DEPLOY_USER variables and DEPLOY_KEY secret — deploys it to your server over ssh. Set API_URL in /srv/blog-dart/.env on the server to reach your API.


Part of devai.io — the blog frontend series, one API and four clients: blog-react, blog-angular, blog-dart, blog-flutter.