An artisan-style CLI
make:model, make:controller, make:request, migrate, queue:work, route:list. Generators write the boring parts so you start from working code.
aso make:model Post -mcrv0.2 · early release · MIT
FastAPI on the server, React on the client, Inertia in between. Routing, an async ORM, auth, queues and an artisan-style CLI, ready on day one.
Needs uv, Python 3.12+ and Node 22+.
$ uvx aso new myapp Creating a new Aso application in ./myapp... $ cd myapp && uv run aso dev App http://127.0.0.1:8000 <- open this one
How it reads
A route, a controller, a validated request and a React page. No serializers, no API client, no glue.
from aso.routing import Route
from app.http.controllers.post_controller import PostController
with Route.middleware("auth").group():
Route.get("/posts", [PostController, "index"]).name("posts.index")
Route.post("/posts", [PostController, "store"]).name("posts.store")class PostController(Controller):
async def index(self):
posts = await Post.with_("author").latest().paginate(15)
return inertia("posts/index", {"posts": posts})
async def store(self, form: StorePostRequest):
await Post.create(**form.validated(), user_id=auth().id())
return redirect().route("posts.index").with_flash("success", "Post created.")from pydantic import Field
from aso.http import FormRequest
class StorePostRequest(FormRequest):
title: str = Field(min_length=3, max_length=255)
body: strimport { Form } from '@inertiajs/react';
import { store } from '@/routes/posts';
export default function Index({ posts }: { posts: Paginated<Post> }) {
return (
<>
{posts.data.map((post) => <PostCard key={post.id} post={post} />)}
<Form {...store.form()}>
<input name="title" />
<textarea name="body" />
<button>Publish</button>
</Form>
</>
);
}What's included
If you know Laravel, you already know where things go. If you know Python, you already know how they work.
make:model, make:controller, make:request, migrate, queue:work, route:list. Generators write the boring parts so you start from working code.
aso make:model Post -mcrControllers return Inertia pages with their props. The browser gets a real React app; you never write fetch calls or JSON endpoints for your own UI.
inertia("posts/index", {...})Models, relationships, scopes, eager loading and pagination on SQLAlchemy 2. Lazy loads raise instead of silently hitting the database.
await Post.with_("author").paginate(15)Registration, login, password reset, email verification and profile settings, already wired to pages in the React starter kit.
Route.middleware("auth")Your Python routes become typed TypeScript helpers. Rename a route and the compiler tells you every link that broke.
aso routes:generateJobs, a worker, mailables, cache and file storage with the same small, consistent API. Local mail lands in a log so nothing leaks while you build.
aso queue:workBuilt for people and agents
Every app ships with an AGENTS.md and architecture tests that run with your suite. Raw SQL with f-strings, queries in controllers, models that import HTTP code: they fail the build, whoever wrote them.
$ uv run pytest tests/architecture
✓ A1 controllers don't import sqlalchemy
✓ A2 models work without a request
✓ A3 input arrives through a FormRequest
✓ A4 every Inertia page exists
✓ A6 no interpolated SQL
… 13 rules, all green
Get started
Python deps, npm packages and the first migration, in one go.
Server, Vite and the route watcher (plus a queue worker if you use one). Port taken? It picks the next free one.
Model, migration, controller and form requests. Then open resources/js/pages.
Aso is young and moving fast. Try it, break it, and tell us what you'd want next.