Skip to content

Directory Structure

The default arvel new application ships with a directory layout that scales from a one-file prototype to a large app without rearranging anything. Nothing here is mandatory — arvel discovers your code through the bootstrap file, not by convention over the filesystem — but the defaults are a good place to start.

myapp/
├── app/                # your application code
│   ├── controllers/    # route controllers
│   ├── models/         # ORM models
│   ├── providers/      # your service providers
│   └── __init__.py
├── bootstrap/          # how the app is assembled
│   ├── app.py          # create_app() — the one place that configures + builds the app
│   ├── providers.py    # your registered providers
│   └── middlewares.py  # the global/group middleware stack
├── config/             # configuration, one module per section
│   ├── app.py
│   ├── database.py
│   ├── session.py
│   └── …
├── database/
│   ├── migrations/     # schema migrations
│   ├── seeders/        # database seeders
│   └── factories/      # model factories
├── resources/
│   └── views/          # Jinja2 templates
├── routes/
│   ├── web.py          # stateful, browser-facing routes (the "web" group)
│   ├── api.py          # stateless JSON routes (the "api" group, /api prefix)
│   └── console.py      # CLI command definitions
├── tests/
├── asgi.py             # the ASGI entrypoint (asgi_app)
├── pyproject.toml
└── .env                # environment values (never committed)

The bootstrap directory

bootstrap/app.py is the heart of the app — the single place that configures and builds it. Both the CLI (arvel <command>) and the ASGI entrypoint load the app from here via create_app():

# bootstrap/app.py
from pathlib import Path
from arvel.kernel import Application

BASE_PATH = Path(__file__).resolve().parent.parent

def create_app() -> Application:
    return (
        Application.configure(str(BASE_PATH))
        .with_config_dir(BASE_PATH / "config")
        .with_providers(BASE_PATH / "bootstrap" / "providers.py")
        .with_middlewares(BASE_PATH / "bootstrap" / "middlewares.py")
        .with_routing(
            web=BASE_PATH / "routes" / "web.py",
            api=BASE_PATH / "routes" / "api.py",
            console=BASE_PATH / "routes" / "console.py",
        )
        .create()
    )

bootstrap/providers.py lists your service providers (framework and installed-package providers are auto-discovered via entry points — you only register your own), and bootstrap/middlewares.py defines the middleware stack. See Service Providers and Middleware.

The app directory

app/ holds the code you write — controllers, models, providers, and anything else your application needs (jobs, events, policies, services). It starts nearly empty; grow it as you go. There's no framework rule about sub-package names — app/services/, app/jobs/, app/policies/ are all just packages you import.

The config directory

Every configuration section is a plain Python module under config/, auto-loaded into the config("section.*") store at boot. Each reads the environment with sensible defaults — see Configuration.

The routes directory

Routes are split by group. web.py carries stateful, browser-facing routes (cookies, sessions, CSRF); api.py carries stateless JSON routes under an /api prefix; console.py defines custom CLI commands. See Routing and Console.

The database directory

database/migrations/ holds your schema migrations, database/seeders/ your seeders, and database/factories/ your model factories — the three covered in Migrations, Seeding, and Factories.

The resources directory

resources/views/ holds Jinja2 templates rendered by the view system. Frontend assets (if you use the Vite integration) live alongside your JS/CSS toolchain — see Frontend.