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¶
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:
Then apply migrations:
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 raisesModelNotFoundErrorwhen the row is missing. The HTTP layer translates that to a 404 automatically.
Running It¶
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:
Next Steps¶
- Validate request bodies with form requests.
- Shape responses with API resources.
- Add authentication.