Guides

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 safe

Vadyl 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.