Compare

A Laravel-style framework for Cloudflare Workers.

Yes. @popcomputer/web, popweb for short, is a batteries-included Effect framework in the spirit of Laravel, and it runs on Cloudflare Workers. It keeps Laravel’s request-to-page workflow: a route names a record, an action loads it and checks the form, and a React page renders the result through Inertia, as Laravel’s React starter kit does. Sign-in, validation, route model binding, queues, scheduled jobs and caching are there from the start, in TypeScript rather than PHP.

Laravel’s pieces in popweb

Most of what you reach for in Laravel has a counterpart. Each line links Laravel’s page, then popweb’s doc for it.

  • Routes and route names: each path is declared once, in src/paths.ts, with Laravel-style {placeholders}, and routes.get or routes.post registers an action on it, with a name if you like. Links and redirects are built from the declared path, so a misspelled one is a type error. Actions and routes
  • Controllers become actions: one Effect per route, wrapped in action(), that ends with render, redirect or back. Responses and failures
  • Form Requests become schemas: each form’s fields are an Effect Schema, read with validateRequest. Keys the schema doesn’t declare are dropped, and a form that fails goes back to its page with an error beside each field. Validating input
  • Route model binding becomes routeBinding: name the table once, and bound returns the row the path names. The binding also says who may load it: ownedBy for its owner, publicWhen or public for anyone, guarded when a guard decides. Route model binding
  • Policies and gates become the binding’s rule, guard() and authorize(). A record the visitor may not see is the same 404 as one that doesn’t exist, authorize() with a rule refuses with a 403, and a guard() covers a group of routes, such as an organisation’s. Guards and roles
  • The starter kits and Fortify become Better Auth, through @popcomputer/web/auth: sign-in and sign-up actions, email verification and slowed password guessing, with Better Auth’s plugins for two-step sign-in, magic links, organisations and OAuth providers. Signing in and out
  • Inertia stays Inertia: the same protocol, with React pages. Inertia’s client keeps its partial reloads, asset versions, error bags and redirects. Pages and props
  • Migrations become Drizzle migrations: drizzle-kit generates them from src/schema.ts, and Wrangler applies them to D1. The database
  • Queues become Cloudflare Queues: routes.queue takes a schema for the message and the work to do with it, which runs with the app’s services. A message whose work fails is left for the queue to retry. Background work
  • The scheduler becomes Cron Triggers, through routes.scheduled. Work that only has to outlive the response, such as sending an email, goes in background(). Background work
  • The cache becomes the page cache and the data cache. A page that reads nothing about the visitor is cached at Cloudflare’s edge until the next deploy. Computed data is kept in KV under tags, and the writes that change it clear it. Page caching and the data cache
  • .env becomes a declared env: src/env.ts names each var and secret with its schema, each isolate checks them before it serves, and a script checks them before a deploy. Vars and secrets
  • Forge and Laravel Cloud, and Vapor, become a deploy to Workers. There’s no server to provision: Wrangler uploads the Worker, and the demo’s deploy checks a new version at its own address before it goes live. Deploying

What’s different

The workflow is Laravel’s. The parts under it aren’t, and these are the differences you’ll notice first:

  • Drizzle, not Eloquent. There are no model classes with relationships and save methods. You write Drizzle queries against typed tables, inside dbQuery, dbMutation, dbTransaction or dbBatch, and a bound record is the table’s row.
  • No Blade or Livewire. Every page is a React component, sent its props by Inertia. If your team builds with Blade and Livewire, that’s a real loss.
  • TypeScript and Effect, not PHP. An action is an Effect that names the services it uses and the failures it can end with, and registering it is a type error until the app provides the first and answers the second. That’s more to learn than a controller method. It’s also what lets the type checker name the problem at the route.
  • D1’s limits. D1 is built on SQLite. Each database holds up to 10 GB on the paid plan, a limit Cloudflare says it can’t raise, and there are no interactive transactions, so writes that must land together go in one dbBatch. On Postgres through Hyperdrive, dbTransaction runs writes and the logic between them together.
  • Pages render in the browser. Laravel’s React, Vue and Svelte starter kits can render Inertia pages on the server. popweb sends each page’s props, and React renders them in the browser.

A monolith web app on Cloudflare Workers

A popweb app is a monolith: one Worker, one repository, one deploy. The same Worker answers the pages, the form posts and any JSON endpoints, runs any Cron Triggers and works through any Queues, and the browser’s scripts and styles are uploaded beside it as static assets. There’s no separate API to build for the React side: Inertia, which calls itself the modern monolith, sends each page its props from the server.

There’s no queue worker, scheduler process or cache server to keep running beside it, either: Cloudflare runs each of them for the Worker. The demo declares the whole shape in one wrangler.jsonc: its D1 database, the sign-in limiter, a nightly Cron Trigger and the client bundle.

When to stay on Laravel

Laravel has years of packages, tutorials and people who know it. Stay on it if:

  • Your Laravel app works. A rewrite buys you Workers and costs you every model, view and test you have.
  • Your team writes PHP, or builds its pages with Blade and Livewire.
  • You lean on Eloquent’s relationships, or on Laravel’s first-party packages.
  • You need pages rendered on the server, which Laravel’s starter kits can do with Inertia and popweb can’t yet.
  • Your work needs long-running processes on a server you control.

Other Laravel alternatives for TypeScript

If what you want is Laravel’s way of working in TypeScript, these are the frameworks people name, each described from its own pages. Of them, Guren and popweb run on Cloudflare Workers.

  • AdonisJS is the closest to Laravel’s own design: routes and controllers, the Lucid ORM in Eloquent’s active-record style, VineJS validation, auth, mail, queues and cache, with Inertia for React or Vue pages, rendered on the server if you choose. It runs on Node.js. If you want Laravel in TypeScript and a server suits you, start there.
  • Wasp describes an app in a TypeScript spec, and builds a React client and a Node.js server with Prisma from it, with auth, background jobs and email included. Its CLI deploys to Fly.io and Railway. Wasp’s own comparison sends people who want Laravel’s idioms to AdonisJS, and those who want its productivity to Wasp.
  • Guren is the nearest to popweb: Laravel-style conventions on Hono, Drizzle and Inertia with React, built for coding agents. It’s Bun-first and deploys to Bun, AWS Lambda, Vercel or Cloudflare Workers, with controllers, Eloquent-style queries run by Drizzle, and pages rendered on the server. popweb differs in its Effect actions, in saying who may load a record once on its binding, and in its agent fence.
  • NestJS calls itself a framework for building server-side Node.js applications, organised into modules with dependency injection and decorators. It’s often named beside AdonisJS when people ask for Laravel in Node.

Questions people ask

Is there a Laravel for JavaScript?

Not one that copies all of it, but several come close in different ways. AdonisJS is the closest to Laravel’s design, on Node.js. Wasp aims at Laravel’s batteries-included feel with React, Node.js and Prisma. Guren and popweb bring Laravel’s conventions to TypeScript with Inertia and React, and both run on Cloudflare Workers. popweb is the one built on Effect.

Can Laravel run on Cloudflare Workers?

Not on Workers itself. Cloudflare lists JavaScript, TypeScript, Python and Rust, plus languages compiled to WebAssembly, and PHP isn’t among them. The nearest Cloudflare gets is Containers, on the paid plan, which run code in any language beside a Worker: a container with its own runtime, controlled by a Worker, rather than a Worker. Laravel’s own hosting is Laravel Cloud and Forge, and Vapor on AWS.

Does popweb use Inertia like Laravel does?

Yes. It speaks the same Inertia protocol, so Inertia’s React client works as you know it: links and forms make Inertia visits, a failed form comes back with its errors, and partial reloads and asset versions behave the same. The differences are that popweb’s docs cover React pages only, each page’s props are declared as a schema, and pages aren’t rendered on the server. Pages and props has the details.

Does popweb have an ORM like Eloquent?

No. It uses Drizzle, where you query typed tables rather than calling methods on model objects. Route model binding loads rows from those tables, and the same tables give each page its fields. drizzle-kit generates the migrations, as The database shows.

Can I move a Laravel app to popweb?

Not automatically: it’s a rewrite, from PHP to TypeScript. The React pages are the part closest to moving, since both send them their props over Inertia, though each page’s props become a schema. Models, policies, Blade views and tests are written again. The map above shows where each piece goes.