FrontierJS

Comparison

FrontierJS vs Laravel

Laravel is the framework FrontierJS most resembles, and the one it borrowed the most from: a complete stack rather than a library, batteries in the box, one CLI, and a strong opinion about where things live. If you know Laravel, you can read almost all of FrontierJS by translation.

This page is the translation table, the three real differences, and an honest account of where Laravel is simply the better answer today.

Stated plainly first: Laravel is a mature framework with a decade of production behind it, a vast package ecosystem, and a hiring pool. FrontierJS is alpha — its APIs move, several packages are not on npm yet, and its auth is thin.

This is a comparison of shape, not of readiness. If you are choosing a framework to run a business on this quarter, the honest recommendation is at the bottom of this page, and it is not always this one.

Translation

If you know Laravel, you know most of this

Nearly every concept has a counterpart. The interesting rows are the last group, where there is no clean translation in either direction.

Data

LaravelFrontierJS
Eloquent→litestone
Migrations (PHP schema builder)→diffed from schema.liteYou edit the schema; the migration is generated from the difference, not written.
Model casts, $fillable, mutators→field attributes@trim @lower @length(1,200) — declared on the field, applied on the way in.
Policies · Gates→@@allow · @@gateDeclared on the Model and compiled into the query, rather than checked in the controller.
Global scopes→row policies
Database drivers (MySQL, Postgres, SQLite…)→SQLite onlyA deliberate narrowing, and the reason the same engine can run in the browser. Also a real limit — see below.

API

Resource controllers→createService({ model })CRUD is derived from the Model rather than scaffolded into a class you own.
Form Requests · $request->validate()→derived from the fields
Middleware→hooksaround · before · after · error, per method or all.
Eloquent API Resources→the result envelopeOne shape for every service instead of a transformer per resource.
Service providers→plugins{ name, register, boot, ready, shutdown, requires }
Queues · Horizon→app.jobsCaravan — a queue on SQLite, in the same process. No Redis, and no Horizon either.
Scheduler (schedule:run + cron)→app.jobs.schedule()In-process, no crontab entry to install.
Mail · Mailables→mailer battery
Notifications (multi-channel)→app.notifyThe closest thing on the page: a class with via() and a method per channel. FrontierJS took the design.
Broadcasting · Echo · Reverb→app.channelDeclared as channel: on the service, announced once from the call, not from each handler.
Sanctum · Passport · Fortify→@frontierjs/authFar thinner. No OAuth, no first-party social login.
Telescope · Pulse→/health · /metrics · /manifestEndpoints, not a UI.

UI

Blade · Livewire→.mesaA compiler with signals, closer to Svelte than to a template engine.
Inertia→sierraFile-tree routing plus createResource binding to the API.
Vite integration · Sail→fli dev
Markdown mailables→email-kitReal components rendered to table-based HTML, from the same language as the app.
Tailwind (via Breeze/Jetstream)→@frontierjs/cssSemantic classes and themes rather than utilities. A different philosophy, not a better one.

Tooling

Artisan→fli
artisan make:*→fli make:*Generators exist in both. The difference is how much they need to generate.
Tinker→litestone studio
Pest · PHPUnit→bring your ownSuite is a named realm with no package behind it yet. Tests here are bun test / vitest.
Factories · Seeders→no equivalentA real gap. You write seed scripts by hand.
Forge · Vapor · Cloud→no equivalentSelf-hosting is the only path. For some readers that is the feature; for others it is the dealbreaker.

No clean translation

Nothing here maps back — the validation rules, live in the browser→resource.fieldsLaravel's answer is Precognition: run the real server rules over the wire. Same goal, different mechanism — a round trip and a running server versus a local check from the same schema.
Nothing here maps back — the model's permissions, in the browser→resource.can()Blade's @can already does this for server-rendered pages. The gap opens in an SPA, where you hand-pass can booleans as Inertia props and keep them in sync yourself.
Eloquent, Blade, queues, notifications as separate ecosystems→one schema under all of them

The same feature, both ways

A Lead resource: validated, owner-scoped, paginated

Same requirements on both sides. Only the owner may read a lead; a member may create and update; an admin may delete. Name, email and value are validated. The list is paginated.

Laravel6 files · mostly written by artisan make:model -a

database/migrations/…_create_leads_table.php

Schema::create('leads', function (Blueprint $table) {
    $table->id();
    $table->foreignId('owner_id')->constrained('users');
    $table->string('name', 200);
    $table->string('email')->unique();
    $table->decimal('value', 10, 2);
    $table->timestamps();
});

app/Models/Lead.php

class Lead extends Model {
    protected $fillable = ['name', 'email', 'value'];
    protected $casts    = ['value' => 'decimal:2'];

    public function owner() { return $this->belongsTo(User::class); }
}

app/Http/Requests/StoreLeadRequest.php

public function rules(): array {
    return [
        'name'  => ['required', 'string', 'max:200'],
        'email' => ['required', 'email', 'unique:leads'],
        'value' => ['required', 'numeric', 'min:0'],
    ];
}

app/Policies/LeadPolicy.php

public function view(User $user, Lead $lead): bool {
    return $lead->owner_id === $user->id;
}
public function create(User $user): bool { return $user->isMember(); }
public function update(User $user, Lead $lead): bool { … }
public function delete(User $user, Lead $lead): bool { return $user->isAdmin(); }

app/Http/Controllers/LeadController.php

public function index(Request $request) {
    return LeadResource::collection(
        Lead::where('owner_id', $request->user()->id)   // or a scope
            ->paginate($request->integer('per_page', 20))
    );
}
public function store(StoreLeadRequest $request) {
    $this->authorize('create', Lead::class);
    return new LeadResource(Lead::create($request->validated()));
}
// show, update, destroy…

routes/api.php + the frontend

Route::apiResource('leads', LeadController::class)
     ->middleware('auth:sanctum');

// then, in an Inertia/SPA frontend:
//  · re-declare the validation rules, or call Precognition
//  · pass `can` booleans through as props to hide the buttons
FrontierJS3 files · nothing elided

db/schema.lite

model Lead {
  id        Int      
  ownerId   Int
  name      String   (1, 200) 
  email     String     
  value     Float    (0)
  createdAt DateTime (now())

  // read · create · update · delete
  (0.4.4.5)
  (read, ownerId == auth().id)
}

api/src/services/leads.service.ts

// name  ← this filename   ('leads.service.ts' → /api/leads)
// model ← the service name ('leads' → db.lead)
// db    ← app.db, scoped per request to the calling user
// CRUD · 401s · 400s ← the Model

export default createService({})

web/src/routes/leads/index.mesa

<script>
  const leads = createResource('leads')
  await leads.find({ $limit: 20 })
</script>

<table class="table">
  {#each leads.data as lead}
    <tr><td>{lead.name}</td><td>{lead.email}</td></tr>
  {/each}
</table>

{#if leads.can('delete')}
  <button class="btn danger">Delete</button>
{/if}

The honest read of this comparison: Laravel's six files are mostly generated by one command, and many developers prefer them — they are explicit, greppable, and trivially customized when a requirement stops being standard.

The difference is not the file count on day one. It is that the validation rule, the ownership rule and the permission levels exist once, so the browser can read the same ones the migration came from.

The service file really is that short — it is empty because everything in it would be a restatement of the schema. Add channel: 'leads' to opt into live updates, which the left-hand column would need Reverb and Echo for.

Beyond the translation

Three differences that are not cosmetic

Everything above is a naming difference. These three are structural — they change what is possible, in both directions.

1 · Derived, not scaffolded

artisan make:model -a writes a controller, a request and a policy into your repo. They are yours: readable, editable, and now your responsibility to keep matching the schema. FrontierJS reads the schema at runtime instead, so there is no generated file to drift.

The cost: less to grep. When behavior surprises you, the answer is in a declaration and a framework, not in a file you can open.

2 · Authorization under the data

A Laravel Policy is checked where you call authorize(). Forget the call — in a console command, a job, a new endpoint — and nothing stops the read. @@allow compiles into the query, so the filter is beneath every caller including the ones written later.

The cost: policy expressions are a small language, not PHP. Anything that needs a service call has to be a hook instead.

3 · The schema crosses the wire

Laravel's model layer stops at the response. Blade and Livewire hide that by rendering on the server; the moment you go SPA, the browser needs its own copy of the rules. Here the field rules and the gate travel to the client and are read there — as an affordance, with the server still enforcing.

The cost: you inherit a JavaScript frontend stack whether you wanted one or not. Laravel lets you never write JS at all.

One language, one runtime

PHP on the server and JavaScript in the browser is two languages, two package managers and two mental models — a cost Laravel developers pay so routinely they stop counting it. FrontierJS is TypeScript throughout, on Bun, with the same hook vocabulary on both sides.

SQLite, on purpose — and only SQLite

No database server to run, back up or connect to; the app is a process and a file. That is also the sharpest limitation on this page: no MySQL, no Postgres, no read replicas, no managed RDS. Laravel runs on all of them, and if your infrastructure is already Postgres, that alone settles it.

Feature by feature

What each one actually has today

● shipped  ·  ◐ partial or narrower  ·  ○ absent. FrontierJS's column is graded against its own CLAUDE.md, not against its ambitions.

CapabilityLaravelFrontierJSNote
ORM + migrations●●Migrations here are diffed from the schema rather than written.
Validation●●Laravel: declared in Form Requests. FJS: derived from field attributes.
Authorization●●Policies checked at call sites vs. policies compiled into the query.
Auth scaffolding · sessions●◐FJS auth ships a provider and /auth/*, but cookie auth is unfinished.
OAuth · social login●○Socialite has no counterpart. Not started.
Queues · scheduler●●FJS runs both on SQLite in-process; no Redis, and no Horizon-style UI.
Mail●●
Multi-channel notifications●●Same design; FJS ships in-app, email and a live event.
Realtime broadcasting●●Reverb/Echo vs. declared channels announced from one place.
Outbound HTTP boundary◐●Laravel has an HTTP client; FJS has declared targets, so egress is enumerable.
API description◐●Laravel usually reaches for a package; FJS derives /manifest.
Server-rendered UI●◐Blade/Livewire are mature. FJS has static prerender with islands, not a Livewire equivalent.
SPA frontend●●Inertia + React/Vue/Svelte vs. Mesa + Sierra.
Client-side rules from the schema◐●Precognition round-trips to the server; FJS reads the same rules locally.
Email templating●●Markdown mailables vs. the app's own components.
Browser extension target○◐jetty exists and works; it calls itself pre-production.
Admin panel●◐Nova/Filament vs. litestone's studio, which is a data browser, not an admin framework.
Testing framework●○Pest/PHPUnit with HTTP and DB helpers. FJS: bring your own runner.
Factories · seeders●○A real gap.
Observability UI●◐Telescope/Pulse vs. /health, /metrics, /manifest.
First-party hosting●○Forge/Vapor/Cloud. FJS self-hosts, by design and by necessity.
Package ecosystem●○Thousands of packages against fourteen. Not close, and won't be soon.
Database engines●○MySQL, Postgres, SQLite, SQL Server vs. SQLite only.
Production track record●○A decade of it vs. none. This row outweighs several others.

Honestly

Where Laravel is the better answer

Laravel, without argument

  • It is finished. A decade of production, semantic versioning you can trust, and upgrade guides for every release.
  • The ecosystem. Cashier for billing, Scout for search, Socialite for OAuth, Horizon for queues, Nova and Filament for admin — all maintained, all documented.
  • The documentation. Among the best in any language, plus books, courses and fifteen years of Stack Overflow.
  • You can hire for it. A Laravel developer starts contributing on day one. Nobody has FrontierJS on their CV.
  • Any database. Postgres, MySQL, read replicas, managed instances — whatever your infrastructure already is.
  • You can avoid JavaScript entirely. Blade and Livewire are a complete answer for interactive UI without a frontend stack.
  • Paid support paths exist. Forge, Vapor, Cloud, and consultancies who will take your call.

What FrontierJS is doing differently

  • One schema across all three realms — the migration, the API's validation and the browser's form rules are the same declaration, not three copies.
  • Authorization beneath the query, so a new endpoint or a background job inherits it rather than re-checking it.
  • One language and one hook vocabulary from the database to the DOM.
  • No database server — the app is a process and a file, which makes local development and small deployments genuinely small.
  • One component set with several targets — SPA, prerendered HTML with islands, transactional email, browser extension.
  • No hosted service in the design. MIT, no account, no phone-home; self-hosting is the default path rather than the fallback.

The recommendation

Which one you should actually use

Use Laravel if…

  • You are shipping something a business depends on, this quarter
  • Your infrastructure is already Postgres or MySQL
  • You need OAuth, billing, search or an admin panel and you need them now
  • You will hire for this codebase, or hand it to someone else
  • You want to write no JavaScript
  • “Alpha” is not a word you can put in a plan

Try FrontierJS if…

  • Keeping the same rule in three places is the tax you most resent
  • You want one language across the whole stack
  • Your app fits comfortably on one machine with SQLite — and most do
  • Self-hosting with no vendor in the loop is a requirement, not a preference
  • You are building something internal, personal, or exploratory, where an alpha framework is a reasonable risk
  • You want to read the whole framework — fourteen packages is a size one person can hold

If you are weighing this seriously: take the schema in the code comparison above, run fli project:new, and see how far it gets in an afternoon. That is a better basis for a decision than any table on this page — and it is the only test that will surface the parts still marked alpha.

Laravel is a trademark of its authors; this page is not affiliated with or endorsed by them.Styled entirely by @frontierjs/css. Home →