Skip to main content

Welcome to Dart Way

Dart Way — full-stack Dart framework on Flutter + Serverpod. Ship real products faster, cleaner, and with minimal boilerplate.

  • 🚀 Build 3–5× faster (typed data layer, ready patterns, fewer layers)
  • 🧱 Feature-first modules, predictable delivery
  • 🔒 CRUD pipeline with access rules, validation, triggers
  • 🔌 Realtime (WebSocket) + built-in loading skeletons & error UX
  • 💬 Community: RUEN

Show me the code

1) UI without state boilerplate

Typed, paginated data straight in widgets. Skeletons & errors handled automatically.

final posts = ref.watchEntityListAsync<Post>(
backendFilter: DwBackendFilter.and([
PostFilter.authorId.equals(currentUserId),
PostFilter.publishedAt.greaterThan(DateTime(2025, 1, 1)),
]),
sortBy: [PostSort.publishedAt.desc()],
);

return posts.dwWhenList(
loadingItemsCount: 6,
childBuilder: (items) => ListView.builder(
itemCount: items.length,
itemBuilder: (_, i) => ListTile(
title: Text(items[i].title),
subtitle: Text(items[i].publishedAt?.toIso8601String() ?? 'Draft'),
),
),
);

2) Typed filters via enums

Instead of raw strings or ad-hoc queries you declare filters once, then use them everywhere — safe and autocompletable.

// lib/filters/post_filter.dart
enum PostFilter<T> with DwFilterMixin<T> {
authorId<int>,
publishedAt<DateTime>,
slug<String>,
}

Usage:

DwBackendFilter.and([
PostFilter.authorId.equals(currentUserId),
PostFilter.publishedAt.lessOrEqual(DateTime.now()),
]);

3) One-line saves from UI

Generic repo handles persistence — no DTO glue or boilerplate.

await ref.saveModel(
Post(
title: 'Hello, Dart Way',
body: 'Minimal code, maximal progress.',
authorId: currentUserId,
publishedAt: DateTime.now(),
),
);

4) Backend rules as a pipeline

Access rules → validation → preprocessing → side-effects. All executed in the right order and properly logged.

final postInsert = InsertConfig<Post>(
allowInsert: (session, post) async =>
session.authenticated && session.userId == post.authorId,
insertValidation: (session, post) {
if (post.title.isEmpty) throw ValidationException('Title is required');
},
beforeInsertPreProcessing: (session, post) async {
post.slug = Slugify.create(post.title);
},
afterInsertSideEffects: (session, post) async {
await Realtime.push('posts:new', post);
},
);

Future<Post> createPost(Session session, Post input) =>
insertWithConfig(session, input, postInsert);

5) Define once, get full stack

Model in Serverpod → typed code on client + server.

# server: lib/src/protocol/post.yaml
class: Post
table: post
fields:
authorId: int
title: String
body: String?
slug: String?
publishedAt: DateTime?

Then:

serverpod generate

Then init it Flutter:

# flutter: lib/core/default_models.dart
DwRepository.setupRepository(
// Used for Skeletons
defaultModel: Post(
id: DwRepository.mockModelId,
userId: DwRepository.mockModelId,
title: 'My Default Post',
body: 'Very interesting text on Flutter development',
slug: 'my-default-post',
publishedAt: DateTime(2025, 6, 1),
),
);

Now Post exists both in Flutter and server code, ready for DwRepository.


Why teams pick Dart Way

  • Less code, fewer layers. No hand-rolled repos or state glue.
  • Predictable delivery. CRUD pipeline makes rules explicit and testable.
  • Full-stack Dart. One language across UI, backend, and models.

👉 Next: Quick Start → to set up a project and see it working.