Your first model
Write a small shop model step by step, check it, format it, look at its graph and open it in the IDE.
This tutorial builds a small shop: products, orders, a way to place an order and an API that lists placed orders. It touches four of the five layers and every CLI command you need day to day. You need the CLI.
1. Start with the domain
Create a folder models and a file models/shop.appling:
module shop {
entity Product {
name: String unique
price: Money
}
}
Every declaration lives in a module. An entity is something your application stores; its fields have a name and a type, and modifiers such as unique add rules. Check it:
npx appling check models
1 file(s), 0 error(s)
2. Add orders and their lifecycle
An order references a product, has a quantity and moves through states. Replace the file with:
module shop {
export Order
enum OrderStatus {
draft
placed
cancelled
}
entity Product {
name: String unique
price: Money
}
entity Order {
product: Product
quantity: Int
status: OrderStatus state default draft
invariant "quantity is positive" { quantity > 0 }
}
action PlaceOrder on Order {
requires { product.price > 0 } else FreeProduct
errors FreeProduct
}
action CancelOrder on Order {
input {
reason: String?
}
}
machine OrderFlow on Order.status {
transition draft -> placed via PlaceOrder
transition draft, placed -> cancelled via CancelOrder
}
}
flowchart TB
subgraph m0["shop"]
n0["OrderStatus<br/><small>enum</small>"]:::domain
n1["Product<br/><small>entity</small>"]:::domain
n2["Order<br/><small>entity</small>"]:::domain
n3["PlaceOrder<br/><small>action</small>"]:::behavior
n4["CancelOrder<br/><small>action</small>"]:::behavior
n5["OrderFlow<br/><small>machine</small>"]:::behavior
end
n2 -->|"type"| n1
n2 -->|"type, uses"| n0
n3 -->|"entity, uses"| n2
n4 -->|"entity"| n2
n5 -->|"field"| n2
n5 -->|"action"| n3
n5 -->|"action"| n4
n3 -->|"uses"| n1
classDef domain fill:#3d2510,stroke:#3d2510,color:#f7f2ea
classDef behavior fill:#6b3f12,stroke:#6b3f12,color:#f7f2ea
classDef application fill:#a85f17,stroke:#a85f17,color:#f7f2ea
classDef interface fill:#d98a1c,stroke:#d98a1c,color:#0c0a08
classDef experience fill:#f2b632,stroke:#f2b632,color:#0c0a08
classDef cross fill:#1b1712,stroke:#7a6e5e,color:#f7f2eaWhat is new:
enum OrderStatuslists the possible states, andstatusis markedstate, so only a state machine may change it.- The
invariantis a rule that must hold for every stored order. The condition in braces is a typed expression. PlaceOrderandCancelOrderare actions (layer 2).requiresis a precondition with the error it raises;inputdeclares parameters,?makes one optional.OrderFlowowns the transitions ofOrder.statusand says which action triggers each one.export Ordermakes the entity visible to other modules asshop.Order.
The graph above is what Appling builds from the text: declarations colored by layer, references as arrows. Behavior points down to the domain, never the other way around.
3. Let the checker catch a mistake
Change the first transition to draft -> shipped and run check again:
models/shop.appling:35:5 error "shipped" is not a value of OrderStatus.
1 file(s), 1 error(s)
The exit code is 1, so the same command guards your CI. Change it back.
4. Expose it
Add a query, a projection and a REST interface inside the module:
query PlacedOrders from Order {
where { status == placed }
paginate 20
}
projection OrderSummary from Order {
quantity
status
product_name = product.name
}
interface rest ShopApi {
GET "/orders" -> PlacedOrders as OrderSummary
POST "/orders/{id}/place" -> PlaceOrder
}
A query is filtered, sorted and paginated access to an entity (layer 3). A projection is the shape other layers see: the only place where that shape may differ from the entity, here with a field copied from the product. The interface (layer 4) maps endpoints to queries and actions, and reaches entity data only through the projection.
TIP
Try GET "/orders" -> Order instead. The checker rejects it: interfaces reach data through queries and projections, not entities.
5. Format and inspect
Write the canonical form back to the file. Running it twice changes nothing:
npx appling fmt models
Print the semantic graph as JSON, the same graph the IDE, the AI and the generators read:
npx appling graph models
Each element is a node with its qualified name, such as shop.Order.status, and each reference an edge. See the semantic graph.
6. Open the IDE
npx @appling/ide models
Open http://127.0.0.1:4410. The editor and the graph stay in sync: move the cursor to PlaceOrder and the graph selects it; rename a field in the inspector and every reference follows. See the IDE.
Next steps
- Learn every construct in the language overview, starting with the domain layer.
- Split the model into modules that reference each other: modules and exports.
- Generate a draft Django or Ash project from it.

