| packages/daravel_session | Laravel-compatible sessions (file or sessions table), encrypted cookies and CSRF protection |
About Daravel
Daravel is a backend framework for Dart that follows the Laravel 11/12 philosophy: expressive
and elegant syntax, batteries included, and conventions that make common tasks disappear. It keeps
what makes Laravel loved and adds what only Dart can offer: one native executable, static types
from the route to the database, and every CPU core on one port.
Status. Daravel is stable since 1.0.0; the current release is
<!-- release -->1.25.0<!-- /release -->, published on
pub.dev by the verified publisher
framework.daravel.dev. The Dart API follows SemVer on the 1.x line; the Laravel-facing
formats are locked by the conformance suite.
Why Daravel?
- One native bundle.
dartisan build compiles the application ahead of time into machine
code, with SQLite bundled by a build hook. The server needs no Dart SDK, PHP-FPM or Node.js
runtime, and no source code. It answers its first request about 14 ms after starting, and
worker isolates share the compiled code: about 11 MB of idle memory with one worker, 12 MB
with four.
- No reflection, no code generation step.
dart:mirrors does not exist in AOT binaries, and
Daravel does not need build_runner either. dartisan make:* and model:sync write plain Dart
into explicit registries and accessor regions, so the compiler sees exactly the code you read.
- Every core on one port, before you need Redis.
serve --isolates=N runs a supervisor that
respawns crashed workers, stops on a crash loop and drains in-flight requests on SIGTERM. A
coordinator isolate keeps atomic locks, the process cache and Laravel-style fixed-window rate
limits correct across workers; local leases keep throttled routes off the coordinator's hot path.
- Request state stays in its request. Every request, job and command runs in its own
Zone
scope, and facades (Auth, DB, Cache, Log) resolve from it. DB.transaction is
zone-aware too: nested calls become savepoints, queued jobs and dispatchAfterCommit events
wait for the outer commit and are dropped on rollback, and on SQLite other requests wait for an
open transaction instead of running inside it.
- Runs next to Laravel on the same database.
$2y$ bcrypt hashes, Crypt payloads
(AES-256-CBC or GCM, with APP_PREVIOUS_KEYS rotation), Sanctum personal access tokens,
422 and paginator JSON, resources and the migrations, cache, jobs and failed_jobs
tables are Laravel 12's, and Daravel's queue workers leave Laravel's jobs alone. This works on
SQLite, PostgreSQL, MySQL and MariaDB, and is locked by a conformance suite.
Move an application endpoint by endpoint.
- Routes that document themselves.
.validates(StoreUserRequest()), .rules({...}) and
.returns(UserResource.schema, shape: .paginated) make the kernel answer 403/422 before your
handler runs, fill request.validated(), and feed dartisan openapi:generate, which turns
the same Laravel rules (required|email|max:255, items.*.id, confirmed) into OpenAPI 3.1.
- Guard rails included. Password hashing (bcrypt, Argon2id, PBKDF2) runs in compute isolates,
so login spikes do not block the event loop.
dartisan lint rejects SQL string interpolation,
dart:mirrors, print and mutable statics in application code, and classes left out of a
registry. A security regression suite and the conformance suite run in CI.
Measured, not claimed: bench/ builds the servers AOT and reports every
scenario relative to a plain dart:io server in the same run. The numbers above come from the
benchmark workflow on a 4-CPU GitHub Actions Linux runner, where Daravel's GET /ping reaches
55% of plain dart:io throughput with one isolate and 78% with four (the load generator
shares the machine).
Features
| Area | What you get today |
|---|
| Foundation | Service container with singleton/scoped/transient lifetimes, deferred providers, .env + .env.{APP_ENV}, typed config sections, boot-time config validation |
| HTTP | Middleware pipeline with aliases, groups and priority; trusted proxies, CORS, security headers, body limits, request ids, per-route timeouts, defer(), terminable middleware |
| Routing | Radix router, constraints, optional params, groups, resources, route model binding, named routes, signed URLs |
| Validation | ~50 rules with Laravel presence semantics, wildcards (items.*.id), FormRequest, Laravel-12 422 payloads, en/id messages |
| Database | Query builder for SQLite, PostgreSQL and MySQL grammars; length-aware, simple and cursor pagination; savepoints; retries; after-commit callbacks |
| ORM | Hybrid Active Record: casts, accessors/mutators, scopes, soft deletes, observers, eager loading, HasOne/HasMany/BelongsTo/BelongsToMany/HasManyThrough |
| Schema | Create and alter tables, foreign keys, SQLite table rebuilds, migrate:fresh, seeders, model factories with a built-in faker |
| Auth | Argon2id/bcrypt/PBKDF2 hashing off the event loop, AES-256 Crypt, Sanctum-compatible tokens, JWT (HS256/EdDSA), multi-guard auth:token,jwt, Gate & Policies, can: middleware |
| Concurrency | Supervisor + workers sharing one port, readiness barrier, respawn with crash-loop detection, coordinator isolate for process-wide state |
| Cache & limits | Memory (LRU), process, file and database stores; remember with stampede protection, flexible, atomic locks; Laravel-style fixed-window rate limiter (shared by every worker, with adaptive local leases) and |
Laravel ↔ Daravel at a glance
Daravel keeps Laravel's names and semantics and adapts only the syntax to Dart.
// bootstrap/app.php
return Application::configure(basePath: __DIR__)
->withRouting(api: __DIR__.'/../routes/api.php', health: '/up')
->withMiddleware(fn (Middleware $m) => $m->append(RequestId::class))
->withSchedule(fn (Schedule $s) => $s->job(new PruneTokens)->daily())
->create();
// bootstrap/app.dart
Application createApp() => Application.configure(basePath: '.')
.withRouting(api: apiRoutes, health: '/up')
.withMiddleware((m) => m.use(const [RequestIdMiddleware()]))
.withSchedule((s) => s.job(const PruneTokens()).daily())
.create();
// routes/api.php
Route::post('/auth/login', [AuthController::class, 'login'])
->middleware('throttle:login');
Route::middleware('auth:sanctum')->group(function () {
Route::get('/auth/me', [AuthController::class, 'me']);
Route::patch('/users/{user}', [UserController::class, 'update'])
->can('update', 'user');
});
// routes/api.dart
router.post('/auth/login', auth.login)
.middleware(const ['throttle:login']);
router.group(middleware: const ['auth'], (r) {
r.get('/auth/me', auth.me);
r.patch('/users/{user}', users.update)
.middleware(const ['can:update,user']);
});
public function store(Request $request)
{
$data = $request->validate([
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
]);
$user = DB::transaction(fn () => User::create([
...$data, 'password' => Hash::make($data['password']),
]));
UserRegistered::dispatch($user);
return new UserResource($user); // 201: recently created
}
Future<Object?> store(Request request) async {
final data = await request.validate({
'email': 'required|email|unique:users',
'password': [Rules.required(), Rules.min(8), Rules.confirmed()],
});
final user = await DB.transaction(() async => User.create({
'email': data.string('email'),
'password': await Hash.make(data.string('password')),
}));
await Event.dispatch(UserRegistered(user.id, user.email));
return UserResource(user); // 201: recently created
}
// A FormRequest is injected and runs first.
Route::post('/posts', [PostController::class, 'store']);
public function store(StorePostRequest $request)
{
return Post::create($request->validated());
}
public function index(Request $request)
{
$page = $request->integer('page', 1);
$tags = $request->query('tags', []); // ?tags[]=a&tags[]=b
}
// Declared on the route: 403/422 first, and in OpenAPI.
router.post('/posts', posts.store)
.validates(const StorePostRequest())
.returns(PostResource.schema, name: 'Post');
Future<Object?> store(Request request) async =>
PostResource(await Post.create(request.validated().toMap()));
Future<Object?> index(Request request) async {
final page = await request.integer('page', 1);
final tags = request.queryInput['tags']; // ['a', 'b']
}
class SendWelcomeEmail implements ShouldQueue
{
public $tries = 3;
public $backoff = [10, 60];
public function handle(): void { /* … */ }
}
SendWelcomeEmail::dispatch($user)->onQueue('emails');
final class SendWelcomeEmail extends Job {
@override int get tries => 3;
@override List<Duration> get backoff =>
const [Duration(seconds: 10), Duration(seconds: 60)];
@override Future<void> handle(JobContext ctx) async { /* … */ }
}
await Queue.dispatch(SendWelcomeEmail(user.id)).onQueue('emails');
$value = Cache::remember('stats', 600, fn () => Stats::compute());
Cache::lock('report')->block(5, function () {
// exclusive across workers
});
final value = await Cache.remember('stats',
const Duration(minutes: 10), Stats.compute);
await Cache.lock('report').block(const Duration(seconds: 5), () async {
// exclusive across workers
});
$this->postJson('/api/auth/register', $payload)
->assertCreated()
->assertJsonPath('user.email', '[email protected]');
Queue::assertPushed(SendWelcomeEmail::class);
(await client.post('/api/auth/register', json: payload))
.assertCreated()
.assertJsonPath('user.email', '[email protected]');
fake.assertPushed<SendWelcomeEmail>();
Quick start
Requires the Dart SDK ^3.13 (Flutter 3.47+). No system SQLite is
needed: package:sqlite3 downloads a hash-verified SQLite build through a build hook and bundles
it with the app (set hooks: user_defines: sqlite3: source: system in pubspec.yaml to use the
OS library instead).
Create an application with the CLI from pub.dev:
dart pub global activate dartisan_cli
dartisan new blog # api template; --template=minimal for less
cd blog
dart run bin/dartisan.dart migrate --seed
dart run bin/dartisan.dart serve --watch
Or work on the framework itself and run the starter app from a clone:
git clone https://github.com/daravel-dev/daravel.git
cd daravel
dart pub get # resolves the whole workspace
cd apps/daravel_starter
cp .env.example .env
dart run bin/dartisan.dart key:generate # writes APP_KEY to .env
dart run bin/dartisan.dart migrate --seed # users, tokens, cache and jobs tables
dart run bin/dartisan.dart serve --watch # http://127.0.0.1:8000 with hot reload
curl http://127.0.0.1:8000/up # liveness
curl http://127.0.0.1:8000/up/ready # {"status":"ok","checks":{"database":…,"cache":…}}
curl -X POST http://127.0.0.1:8000/api/auth/register \
-H 'content-type: application/json' \
-d '{"name":"Ana","email":"[email protected]","password":"secret-123","password_confirmation":"secret-123"}'
Or start a fresh application from the clone (--path points at the framework checkout;
--source=git depends on this repository through git; the default uses pub.dev):
dart run packages/dartisan_cli/bin/dartisan.dart new ../blog --path=.
cd ../blog
dart run bin/dartisan.dart migrate --seed
dart run bin/dartisan.dart serve --watch
Serve on every core and process background work:
dart run bin/dartisan.dart serve --isolates=4 # supervisor + 4 workers on one port
dart run bin/dartisan.dart queue:work --queue=emails,default
dart run bin/dartisan.dart schedule:work
Ship it as a native bundle (bin/server plus the bundled SQLite in lib/; deploy the directory):
dart run bin/dartisan.dart build # = dart build cli -t bin/server.dart -o build/server
APP_ISOLATES=4 METRICS_ENABLED=true ./build/server/bundle/bin/server
dart run tool/smoke.dart # boots the bundle and probes it
dart run bin/dartisan.dart build --docker # multi-stage Dockerfile, no libsqlite3 needed
Project structure
The starter application mirrors the Laravel 12 layout, so a Laravel developer is never lost:
apps/daravel_starter/
├── bin/
│ ├── server.dart # HTTP entrypoint (compiles to the native binary)
│ └── dartisan.dart # project-bound CLI
├── bootstrap/
│ ├── app.dart # Application.configure(...): routing, auth, events, schedule
│ ├── migrations.dart # explicit registries maintained by `dartisan make:*`
│ ├── seeders.dart
│ ├── jobs.dart
│ ├── policies.dart # registerPolicies(gate)
│ ├── listeners.dart # registerListeners(events)
│ └── commands.dart # custom dartisan commands
├── routes/
│ ├── api.dart # mounted under /api
│ ├── web.dart
│ └── console.dart # scheduled tasks
├── lib/app/
│ ├── console/commands/ # custom dartisan commands
│ ├── events/ # plain event classes
│ ├── http/controllers/ # controllers are plain classes; handlers are method tear-offs
│ ├── http/requests/ # FormRequests
│ ├── http/resources/ # JsonResources
│ ├── jobs/ # queued jobs
│ ├── listeners/ # event listeners
│ ├── models/ # Hybrid Active Record models
│ └── policies/ # authorization policies
├── database/
│ ├── migrations/ # m_<timestamp>_<name>.dart
│ ├── factories/
│ └── seeders/
├── test/
│ ├── feature/ # TestClient and refreshDatabase tests (in-memory SQLite)
│ └── architecture_test.dart # lints, registry completeness, import rules
└── tool/smoke.dart # AOT binary smoke test used by CI
The monorepo is a Dart pub workspace:
The eighteen packages (daravel,
dartisan_cli,
daravel_test,
daravel_lints,
daravel_mysql,
daravel_permission,
daravel_session,
daravel_activitylog,
daravel_excel,
daravel_log_viewer,
daravel_pulse,
daravel_payments,
daravel_medialibrary,
daravel_redis,
daravel_teorion,
daravel_livekit,
daravel_google,
,
,
,
,
) are released together
under one version by the verified publisher
;
only packages from that publisher belong to this project. pub.dev also tags
them as Flutter-compatible because they are plain Dart, but Daravel is a
server framework for Linux, macOS and Windows.
dartisan CLI
| Group | Commands |
|---|
| Project | new <name> [--template=api|minimal] [--path] [--git], about, doctor [--production], key:generate [--show] [--force], stub:publish, env:encrypt, env:decrypt, env:obfuscate, adopt <laravel-path> [--check], down [--secret] [--retry], up, tinker "<expr>" |
| Serve & build | serve [--host] [--port] [--isolates] [--watch] [--embed-queue] [--embed-scheduler], build [--output] [--with-cli] [--docker] [--no-sqlite], test [--conformance] [--security] [--coverage], route:list [--path] [--method] [--name] [--json], openapi:generate, lint [--json], mcp:serve [--allow-write], mcp:install, coordinator:stats |
| Database | migrate [--seed], migrate:status, migrate:rollback [--step], migrate:reset, migrate:fresh [--seed] [--force], db:seed [--class] [--all], model:sync [Model] [--dry-run], model:show <Model> [--json], db:show, db:table <table> [--limit] |
| Generators | make:model [-m -f -s -c -p -r --api --all], make:migration, make:seeder, make:factory, make:controller [--api --resource --model], make:request, make:middleware, make:job, make:policy, make:event, make:listener [--event], make:resource, make:rule, make:observer, make:provider, make:command, make:test [--unit] |
| Queue | queue:work [--queue --tries --backoff --timeout --max-jobs --once], queue:failed, queue:retry, queue:forget, , , |
Generators update the registries in bootstrap/ (migrations, seeders, jobs, policies, listeners
and custom commands) through marked regions, so a new class is wired up the moment it is created.
Every template can be customised after stub:publish:
dart run bin/dartisan.dart make:model Post --all --api
created lib/app/models/post.dart
created database/migrations/m_2026_10_24_090507_create_posts_table.dart
updated bootstrap/migrations.dart
created database/factories/post_factory.dart
created database/seeders/post_seeder.dart
updated bootstrap/seeders.dart
created lib/app/http/controllers/post_controller.dart
created lib/app/policies/post_policy.dart
updated bootstrap/policies.dart
created lib/app/http/resources/post_resource.dart
Architecture in brief
- Container + Zone scopes.
Container.runScoped('request', …) stores the scope in a child
Zone; facades resolve the zone container first and fall back to the per-isolate root, so
DB, Auth, Cache and Log work in requests, jobs, commands and tests alike.
- Request lifecycle. Global middleware → radix-tree routing → route model binding → route
middleware → handler → automatic response conversion, then a terminate phase after the response
is written.
404, 405 (with Allow), HEAD and OPTIONS behave exactly like Laravel.
- State scopes (ADR-003). Every store declares
isolate, process or cluster scope. Rate
limits and locks default to process and are served by a coordinator isolate, so
throttle:60,1 means 60 per minute for the whole server, not 60 per worker.
- Zone-aware transactions.
DB.transaction(() async { … }) puts the transaction executor in
the zone. Queued jobs and dispatchAfterCommit events wait for the commit and are dropped on
rollback, including nested savepoints.
- Secure by default. Parameterised SQL only, hashing in compute isolates, constant-time secret
comparison, downgrade-resistant hash verification, log redaction, trusted-proxy-aware client IPs.
Roadmap
| Tier | Scope | State |
|---|
| 0 | Walking skeleton: container, HTTP kernel, router, SQLite, migrations, test client, CLI, CI | ✅ done |
| 1 | S1 foundation · S2 HTTP hardening · S3 routing · S4 validation · S5 database · S6 ORM · S7 schema, seeding & generators · S8 auth · S9 multi-isolate & cache · S10 events, queue & scheduler · S11 resources, storage & observability · S12 CLI (new, hot reload, build --docker, doctor, openapi:generate, model:sync, stubs) · S13 testing toolkit, lints & architecture tests | ✅ done |
| 1 | S14 conformance suite, security suite, parity.yaml, benchmark harness, documentation site, api/minimal templates → first public beta, then 1.0.0 | ✅ done |
| 1.x | Released after 1.0: localization, feature flags, SSE and WebSockets, the Flutter client generator, driver packages and db:transfer, mail and notifications, Laravel Echo broadcasting, Redis and Valkey (daravel_redis), roles and permissions (daravel_permission), an activity log (daravel_activitylog), query filters (daravel_teorion); latest 1.25.0 | ✅ done |
| 2 | Sessions/CSRF, S3, OpenTelemetry, Blade-compatible compiled views; database clients only in the driver packages | planned |
| daragic | Bidirectional Laravel ⇄ Daravel generator: runtime introspection → IR → generated project, converted tests, differential replay | designed |
| Ecosystem | First-party packages under daravel-dev: daravel_horizon (queue supervisor and dashboard), daravel_socialite (OAuth and Sign in with Apple/Google), then daravel_telescope and daravel_scout | planned |
Documentation
Guides live in docs/guide, in English and in Indonesian
(docs/guide/id): getting started, routing, validation, database,
responses, auth, background work, testing, deployment (Docker, Cloud Run, VPS), Laravel
coexistence, migrating from Laravel, security, performance
and the generated Laravel ⇄ Daravel parity table.
dart run tool/docs/generate.dart builds the HTML site into build/site. Release notes are in
CHANGELOG.md.
Design process
The design is documented before it is built. Architecture, module specifications, the
Laravel ⇄ Daravel generator (daragic) and the slice-by-slice plan live in maintainer blueprints,
with decisions recorded as binding ADRs. Code comments refer to them as BLUEPRINT_V2 §… and
ADR-…. The decisions the runtime depends on most (state scopes, the supervisor and its workers,
the Laravel coexistence defaults) are written up in docs/adr.
Brand
The Daravel mark is an infinity loop: a blue loop for Dart and a red loop for the Laravel
heritage, for an application that runs on both side by side and migrates either way, framed in
gold ornament. Icons from 16 to 1024 px, a transparent mark, single-colour versions, light and
dark lockups, a social preview and favicon.ico live in docs/brand, together
with usage rules. Every file is generated by tool/brand/generate.dart
(dart run tool/brand/generate.dart) from the illustration in docs/brand/source, which is the
source of truth.
Quality gates
Every commit runs on Linux, Windows and macOS: dart format --set-exit-if-changed,
dart analyze --fatal-infos with strict casts, inference and raw types, dart test for every
package (including the Laravel conformance suite, the security suite and the parity.yaml
check), a docs freshness check, dart build cli of the starter and a smoke test against the
resulting binary. Benchmarks run nightly (bench/).
dart format --output=none --set-exit-if-changed .
dart analyze --fatal-infos
(cd packages/daravel && dart test)
(cd packages/daravel_lints && dart test)
(cd packages/daravel_test && dart test)
(cd packages/dartisan_cli && dart test)
(cd apps/daravel_starter && dart test)
(cd packages/daravel && dart test --tags conformance) # Laravel contracts only
(cd packages/daravel && dart test --tags security) # security suite only
dart run tool/docs/generate.dart --check
Sponsoring Daravel
Support the continuous development of Daravel through GitHub Sponsors:
Contributing
Daravel is in its foundation phase and welcomes design discussion as much as code. Open an
issue to propose a design change, or a pull request that keeps every quality gate green.
Commits use DCO sign-off. See CONTRIBUTING.md.
Security vulnerabilities
If you discover a security vulnerability, please report it privately as described in
SECURITY.md instead of opening a public issue.
License & trademarks
Daravel is open-source software licensed under the MIT license.
Code generated by dartisan (new, make:*, model:sync) or daragic in your project
belongs entirely to you, with no additional license restrictions.
Text and closely followed logic from Laravel and Spatie, and their MIT
licenses, are listed in THIRD_PARTY_NOTICES.md.
Trademark disclaimer: "Laravel" is a trademark of Laravel Holdings Inc. "Dart" and "Flutter"
are trademarks of Google LLC. Daravel is an independent open-source project and is not
affiliated with, endorsed by, or sponsored by Laravel Holdings Inc. or Google LLC. Laravel is
referenced only to describe the design inspiration and interoperability (nominative fair use).