magic 1.0.0-alpha.2
magic: ^1.0.0-alpha.2 copied to clipboard
A Laravel-inspired Flutter framework with Eloquent ORM, routing, and MVC architecture.
[Magic Logo]
Magic
The Laravel Experience for Flutter.
Build production-ready Flutter apps with Facades, Eloquent ORM, Service Providers, and IoC Container — zero boilerplate.
Documentation · pub.dev · Issues
Alpha Release — Magic is under active development. APIs may change before stable. Star the repo to follow progress.
Why Magic? #
Flutter gives you widgets, but not architecture. Building a real app means wiring up HTTP clients, auth flows, caching, validation, routing, and state management — all from scratch, every time.
Magic fixes this. If you know Laravel, you already know Magic:
// Before — the Flutter way
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
final response = await dio.get('/users/1');
final user = User.fromJson(response.data['data']);
// ...manually handle tokens, errors, caching, state...
// After — the Magic way
final user = await User.find(1);
Features #
| Feature | Description | |
|---|---|---|
| 🏗️ | IoC Container | Service Container with singleton, bind, and instance registration |
| 🎭 | 16 Facades | Auth, Http, Cache, DB, Event, Gate, Log, Route, Lang, Storage, Vault, Crypt and more |
| 🗄️ | Eloquent ORM | Models, QueryBuilder, migrations, seeders, factories — hybrid API + SQLite persistence |
| 🛣️ | Routing | GoRouter integration with middleware, named routes, and context-free navigation |
| 🔐 | Authentication | Token-based auth with guards (Bearer, BasicAuth, ApiKey), session restore, auto-refresh |
| 🛡️ | Authorization | Gates, policies, MagicCan / MagicCannot widgets |
| ✅ | Validation | Laravel-style rules: Required, Email, Min, Max, In, Confirmed |
| 📡 | Events | Pub/sub event system with MagicEvent and MagicListener |
| 💾 | Caching | Memory and file drivers with TTL and remember() |
| 🌍 | Localization | JSON-based i18n with :attribute placeholders |
| 🎨 | Wind UI | Built-in Tailwind CSS-like styling with className syntax |
| 🧰 | Magic CLI | Artisan-style code generation: magic make:model, magic make:controller |
Quick Start #
1. Add Magic to Your Project #
# Create a new Flutter project (or use an existing one)
flutter create my_app
cd my_app
# Add Magic as a dependency
flutter pub add magic
2. Scaffold with Magic CLI #
Magic CLI is bundled with the package — no global install needed:
# Initialize Magic with all features
dart run magic:magic install
# Or exclude specific features
dart run magic:magic install --without-database --without-auth
The CLI sets up everything: directory structure, config files, service providers, environment files, and bootstraps main.dart.
Tip
For convenience, you can also activate the CLI globally: dart pub global activate magic_cli, then use magic install directly.
3. Build #
class UserController extends MagicController with MagicStateMixin<List<User>> {
static UserController get instance => Magic.findOrPut(UserController.new);
Future<void> fetchUsers() async {
setLoading();
final response = await Http.get('/users');
response.successful
? setSuccess(response.data['data'].map((e) => User.fromMap(e)).toList())
: setError(response.firstError ?? 'Failed to load');
}
Widget index() => renderState(
(users) => ListView.builder(
itemCount: users.length,
itemBuilder: (_, i) => ListTile(title: Text(users[i].name ?? '')),
),
onLoading: const CircularProgressIndicator(),
onError: (msg) => Text('Error: $msg'),
);
}
Facades #
Auth #
// Login with token and user model
final response = await Http.post('/login', data: credentials);
final user = User.fromMap(response['data']['user']);
await Auth.login({'token': response['data']['token']}, user);
// Check authentication
if (Auth.check()) {
final user = Auth.user<User>();
}
// Restore session on app start
await Auth.restore();
// Logout
await Auth.logout();
HTTP Client #
// Standard requests
final response = await Http.get('/users', query: {'page': 1});
await Http.post('/users', data: {'name': 'John', 'email': 'john@example.com'});
await Http.put('/users/1', data: {'name': 'Jane'});
await Http.delete('/users/1');
// RESTful resource helpers
final users = await Http.index('users', filters: {'role': 'admin'});
final user = await Http.show('users', '1');
await Http.store('users', {'name': 'John'});
await Http.update('users', '1', {'name': 'Jane'});
await Http.destroy('users', '1');
// File upload
await Http.upload('/avatar', data: {'user_id': '1'}, files: {'photo': file});
// Response API
response.successful // 200-299
response.failed // >= 400
response.data // Map<String, dynamic>
response['key'] // Shorthand access
response.errors // Laravel validation errors (422)
response.firstError // First error message
Routing #
// Define routes
MagicRoute.page('/home', () => HomeController.instance.index());
MagicRoute.page('/users/:id', () => UserController.instance.show());
// Route groups with middleware
MagicRoute.group(
prefix: '/admin',
middleware: [AuthMiddleware()],
routes: () {
MagicRoute.page('/dashboard', () => AdminController.instance.index());
},
);
// Navigate (context-free)
MagicRoute.to('/users');
MagicRoute.toNamed('user.show', params: {'id': '1'});
MagicRoute.back();
MagicRoute.replace('/login');
Cache #
// Store and retrieve
await Cache.put('key', 'value', ttl: Duration(minutes: 30));
final value = await Cache.get('key');
// Remember pattern — fetch once, cache for duration
final users = await Cache.remember<List<User>>(
'all_users',
Duration(minutes: 5),
() => fetchUsersFromApi(),
);
// Check and forget
if (Cache.has('key')) {
await Cache.forget('key');
}
await Cache.flush(); // Clear all
Eloquent ORM #
class User extends Model with HasTimestamps, InteractsWithPersistence {
// Typed getters — always use get<T>()
int? get id => get<int>('id');
String? get name => get<String>('name');
String? get email => get<String>('email');
// Setters
set name(String? v) => set('name', v);
set email(String? v) => set('email', v);
// Required overrides
@override String get table => 'users';
@override String get resource => 'users';
@override List<String> get fillable => ['name', 'email'];
// Static finders
static Future<User?> find(dynamic id) =>
InteractsWithPersistence.findById<User>(id, User.new);
static Future<List<User>> all() =>
InteractsWithPersistence.allModels<User>(User.new);
}
// CRUD — hybrid persistence (API + local SQLite)
final user = await User.find(1); // SQLite first → API fallback → sync
final users = await User.all();
await user.save(); // POST (create) or PUT (update)
await user.delete(); // DELETE
await user.refresh(); // Re-fetch from API
Validation #
final validator = Validator.make(
{'email': email, 'password': password},
{
'email': [Required(), Email()],
'password': [Required(), Min(8)],
},
);
if (validator.fails()) {
print(validator.errors()); // {'email': 'The email field is required.'}
}
// Or throw on failure
final data = validator.validate(); // Throws ValidationException if invalid
Authorization #
// Define abilities
Gate.define('update-post', (user, post) => user.id == post.userId);
Gate.before((user, ability) {
if (user.isAdmin) return true;
return null; // Fall through to specific check
});
// Check in code
if (Gate.allows('update-post', post)) {
// Show edit button
}
// Check in widgets
MagicCan(
ability: 'update-post',
arguments: post,
child: EditButton(),
)
Forms #
final form = MagicFormData({
'name': user.name ?? '',
'email': user.email ?? '',
});
// In widget tree
MagicForm(
formData: form,
child: Column(children: [
WFormInput(label: 'Name', controller: form['name']),
WFormInput(label: 'Email', controller: form['email']),
WButton(
onTap: () async {
if (form.validate()) {
await form.process(() => controller.updateProfile(form.data));
}
},
isLoading: form.isProcessing,
child: Text('Save'),
),
]),
)
Events #
// Define event
class UserRegistered extends MagicEvent {
final User user;
UserRegistered(this.user);
}
// Dispatch
await Event.dispatch(UserRegistered(user));
// Listen (register in ServiceProvider)
Event.listen<UserRegistered>(() => SendWelcomeEmail());
Service Providers #
class AppServiceProvider extends ServiceProvider {
@override
void register() {
// Sync — bind to container
app.singleton('payment', () => StripeService());
}
@override
Future<void> boot() async {
// Async — other services available
Auth.registerModel<User>(User.fromMap);
}
}
// Register in config
final appConfig = {
'app': {
'providers': [
(app) => AppServiceProvider(app),
],
},
};
Wind UI #
Magic includes Wind UI — a utility-first styling engine inspired by Tailwind CSS. Build UIs with className strings instead of nested widget trees.
WDiv(
className: 'flex flex-col gap-4 p-6 bg-white dark:bg-gray-900 rounded-xl shadow-lg',
children: [
WText('Dashboard', className: 'text-2xl font-bold text-gray-900 dark:text-white'),
WButton(
onTap: _refresh,
className: 'bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg',
child: Text('Refresh'),
),
],
)
See Wind UI Documentation for the complete widget reference and utility class guide.
CLI Commands #
dart run magic:magic make:model User -mcf # Model + migration + controller + factory
dart run magic:magic make:controller User # Controller
dart run magic:magic make:view Login # View class
dart run magic:magic make:migration create_users # Migration
dart run magic:magic make:seeder UserSeeder # Database seeder
dart run magic:magic make:policy Post # Authorization policy
dart run magic:magic make:provider Payment # Service provider
dart run magic:magic make:event OrderShipped # Event class
dart run magic:magic make:listener SendEmail # Event listener
dart run magic:magic make:middleware Auth # Middleware
dart run magic:magic make:request StoreUser # Form request
dart run magic:magic make:lang tr # Language file
dart run magic:magic make:enum Status # Enum class
Tip
If you activated the CLI globally (dart pub global activate magic_cli), you can use the shorter magic <command> syntax instead.
Architecture #
Magic.init() → Env.load() → configFactories → providers register() → providers boot() → app ready
lib/
├── config/ # Configuration files (app, auth, cache, database)
├── app/
│ ├── controllers/ # Request handlers (MagicController)
│ ├── models/ # Eloquent models
│ └── policies/ # Authorization policies
├── database/
│ ├── migrations/ # Schema migrations
│ ├── seeders/ # Database seeders
│ └── factories/ # Model factories
├── resources/views/ # UI view classes
├── routes/ # Route definitions
└── main.dart # Entry point
Documentation #
Full docs at magic.fluttersdk.com.
| Topic | |
|---|---|
| Installation | Setup and requirements |
| Configuration | Environment and config files |
| Service Providers | Provider lifecycle |
| Routing | Routes and navigation |
| Controllers | Request handlers |
| Views | UI layer |
| HTTP Client | Network requests |
| Middleware | Request pipeline |
| Forms | Form handling and validation |
AI Agent Integration #
Use Magic with AI coding assistants like Claude Code, Cursor, or GitHub Copilot. The magic-framework skill teaches your AI the correct patterns — Facades, Eloquent ORM, Service Providers, controllers, routing, and common anti-patterns — so it generates correct Magic code on the first try.
Setup instructions and skill files: fluttersdk/ai
Contributing #
git clone https://github.com/fluttersdk/magic.git
cd magic && flutter pub get
flutter test && dart analyze
Report a bug · Request a feature
License #
MIT — see LICENSE for details.
Built with care by FlutterSDK
If Magic saves you time, give it a star — it helps others discover it.