Define your first entity
A complete walkthrough: Customer, Order, OrderItem with relations, indexes, and a basic access policy.
Entities are the nouns in your product model. This guide walks you through modeling a small e-commerce backend — Customer, Order, OrderItem — with proper relations, indexes, validation, and a basic access policy. By the end you'll have compiled REST + GraphQL + SDK surfaces against real persistence.
1. Customer
// schema/Customer.vadyl.ts
import { entity, field } from "@vadyl/sdk";
export const Customer = entity("Customer", {
id: field.id(),
email: field.string({ unique: true, format: "email" }),
name: field.string({ maxLength: 200 }),
createdAt: field.timestamp({ defaultsTo: "now()" }),
updatedAt: field.timestamp({ updatesOn: "mutation" }),
});field.id() is a typed primary key — Vadyl picks the right representation for the bound database (UUID for Postgres / MongoDB, BIGSERIAL for MySQL, etc.). unique: true on email generates a unique index.
2. Order with a relation
// schema/Order.vadyl.ts
import { entity, field, relation, index } from "@vadyl/sdk";
export const Order = entity("Order", {
id: field.id(),
total: field.decimal({ precision: 10, scale: 2 }),
currency: field.string({ maxLength: 3, defaultsTo: "USD" }),
status: field.enum(["pending", "paid", "fulfilled", "refunded"], {
defaultsTo: "pending",
}),
customer: relation.belongsTo("Customer", { onDelete: "restrict" }),
createdAt: field.timestamp({ defaultsTo: "now()" }),
paidAt: field.timestamp({ nullable: true }),
indexes: [
index.on(["customer", "status"]),
index.on(["createdAt"], { order: "desc" }),
],
});relation.belongsTo declares the foreign-key side; Vadyl infers the inverse relation.hasMany on Customer automatically. onDelete: "restrict" blocks deleting a customer who still has orders.
3. OrderItem (composition)
// schema/OrderItem.vadyl.ts
import { entity, field, relation } from "@vadyl/sdk";
export const OrderItem = entity("OrderItem", {
id: field.id(),
order: relation.belongsTo("Order", { onDelete: "cascade" }),
product: relation.belongsTo("Product"),
quantity: field.integer({ min: 1 }),
unitPrice: field.decimal({ precision: 10, scale: 2 }),
composedOf: "Order", // OrderItems are owned by their Order
});Marking composedOf: "Order" tells Vadyl this is a composition relationship — children share the parent's lifecycle. Combined with onDelete: "cascade", deleting an Order also deletes its OrderItems.
4. A simple access policy
import { entity, field, relation, access, ctx } from "@vadyl/sdk";
export const Order = entity("Order", {
// ... fields
}, {
access: access.model({
readFilter: access.where("customer", "==", ctx.userId),
insertCheck: access.where("customer", "==", ctx.userId),
updateTargetFilter: access.where("customer", "==", ctx.userId),
deleteFilter: access.deny(),
}),
});See the next guide, Add an access policy, for the full surface.
5. Apply the migration
vadyl deploy preview # see the planned DDL
vadyl deploy # apply if safeVadyl classifies the migration. For new tables this is metadata-only on document substrates and instant on relational ones. No backfill required, no approval gate.
6. Hit the compiled APIs
# REST
POST /api/orders
GET /api/orders/{id}
GET /api/customers/{id}/orders
# GraphQL
mutation { createOrder(input: { customer: "...", total: "29.99" }) { id } }
query { customer(id: "...") { name orders { id status total } } }
# Typed SDK
const order = await sdk.orders.create({ customer: customerId, total: "29.99" });
const recent = await sdk.customers.read(customerId, { include: ["orders"] });What you got for free
- Three tables / collections with the right schema for your provider.
- Foreign-key constraints (or document references) wired correctly.
- Composite indexes on (customer, status) and (createdAt desc).
- Cascade delete behavior on OrderItem when an Order is removed.
- REST routes, GraphQL types and resolvers, gRPC service contracts.
- OpenAPI spec and typed SDK clients in TS / Python / C# / Go / Rust.
- Realtime subscriptions for entity changes.
- Operational trails and audit logs on every mutation.
- Quota accounting and billing scope per project.