Skip to content

Quickstart

Introduction

Let's build a small JSON API: a route, a model, a migration, and an ORM-backed endpoint. This assumes you've installed Arvel and run arvel new my-app.

By the end you'll expose GET /api/items and GET /api/items/{item_id}, both backed by a database table.

Defining a Route

A fresh project ships with one route in routes/api.py:

from arvel import Route


@Route.get("/api/healthz", name="api.healthz")
async def healthz() -> dict[str, str]:
    return {"status": "ok"}

Every route handler is a plain async def. Add more with @Route.get, @Route.post, and friends. See Routing.

Creating a Model & Migration

arvel make:model Item
arvel make:migration create_items_table

make:model writes app/models/item.py. Edit it to declare columns with the schema helpers:

from decimal import Decimal

from arvel.database import Model, Timestamps, boolean, decimal, id_, string


class Item(Model, Timestamps):
    __tablename__ = "items"

    id: int = id_()
    name: str = string(255)
    price: Decimal = decimal(10, 2)
    is_active: bool = boolean(default=True)

Each helper (id_, string, decimal, boolean) maps a typed attribute to a database column. Timestamps adds created_at / updated_at. See Models & CRUD.

Note

make:migration writes a file under database/migrations/. Open it and fill in the table's columns (see Migrations) before running it. The model and the migration are separate steps — there is no make:model --migration flag.

Running the Migration

Configure a database in .env. SQLite needs no server:

DB_CONNECTION=sqlite
DB_URL=sqlite+aiosqlite:///database/database.sqlite

Then apply migrations:

arvel migrate

Check status any time with arvel migrate:status. See Migrations.

Querying From a Route

Add to routes/api.py:

from arvel import Route
from app.models.item import Item


@Route.get("/api/items", name="items.index")
async def index() -> list[dict[str, object]]:
    items = await Item.where(is_active=True).order_by("-created_at").get()
    return [{"id": i.id, "name": i.name, "price": str(i.price)} for i in items]


@Route.get("/api/items/{item_id}", name="items.show")
async def show(item_id: int) -> dict[str, object]:
    item = await Item.find_or_fail(item_id)
    return {"id": item.id, "name": item.name, "price": str(item.price)}
  • Item.where(is_active=True) builds a query; order_by("-created_at") sorts descending; .get() runs it and returns model instances.
  • Item.find_or_fail(item_id) loads by primary key and raises ModelNotFoundError when the row is missing. The HTTP layer translates that to a 404 automatically.

Running It

uv run arvel serve --reload
  • GET http://127.0.0.1:8000/api/items[] until you insert data.
  • GET http://127.0.0.1:8000/docs → interactive OpenAPI docs.

Verify with curl:

curl http://127.0.0.1:8000/api/healthz
# {"status": "ok"}
curl http://127.0.0.1:8000/api/items
# []

Next Steps