> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Views and Links

> Define typed business objects over your tables and declare how records relate

Tables store the facts your app writes. Most apps also need *business objects*
built from those facts: a customer together with its order count, an invoice
together with the customer it belongs to. Bijection gives you two schema
declarations for this:

* A **view** computes a keyed, read-only collection from your tables. Each view
  defines an *object type*, and each row is one identified object.
* A **link type** is an ordinary table whose rows associate two records, with
  the cardinality you declare enforced on every write.

Both live in your [schema](/database/schemas), and both are read with the same
`ctx.db` calls you already use in [queries](/functions/query-functions).

Here is a view that summarizes each customer's orders:

```ts bijection/schema.ts {16-33} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineSchema, defineTable, defineView, q } from "bijection/server";
import { v } from "bijection/values";

const orderTotals = q
  .table("orders")
  .groupBy(["customer_id"])
  .aggregate({ order_count: q.count(), total_cents: q.sum("amount_cents") })
  .as("totals");

export default defineSchema({
  customers: defineTable({ name: v.string(), region: v.string() }),
  orders: defineTable({
    customer_id: v.id("customers"),
    amount_cents: v.int64(),
  }).index("by_customer", ["customer_id"]),
  customer_summaries: defineView({
    key: { from: "customers", field: "_id" },
    expression: q
      .table("customers")
      .as("customer")
      .leftJoin(orderTotals, {
        left: "customer._id",
        right: "totals.customer_id",
      })
      .select({
        name: q.field("customer.name"),
        region: q.field("customer.region"),
        order_count: q.field("totals.order_count"),
        total_cents: q.field("totals.total_cents"),
      }),
  }),
});
```

And a query that reads it like any table:

```ts bijection/customers.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { query } from "./_generated/server";
import { v } from "bijection/values";

export const summary = query({
  args: { customer_id: v.id("customers") },
  handler: async (ctx, args) => {
    return await ctx.db.get("customer_summaries", args.customer_id);
  },
});
```

The result is typed from the view's expression. When an order is inserted, every
client subscribed to `summary` for that customer receives the new count, just as
with any other query.

## Views

A view is declared with `defineView({ key, expression })`. The expression is
built with the `q` builder from a fixed set of relational operators: scan,
filter, projection, equality joins and grouping. Bijection checks the whole
definition when you deploy, infers the output type, and evaluates it for you.
You never write to a view: its rows change when its inputs change.

A view is **virtual** by default, which means it's evaluated when it's read.
Add `.materialize()` and Bijection stores its output instead, keeping it current
in the same transaction as every write to its inputs. Both forms return the same
results.

## Links

A link type is a table with two typed ID fields, its *endpoints*, and a
`.link(...)` declaration that states how many links each record may take part
in:

```ts bijection/schema.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
customer_invoices: defineTable({
  customer_id: v.id("customers"),
  invoice_id: v.id("invoices"),
}).link({ customer_id: [0, "many"], invoice_id: [0, 1] }),
```

Here a customer can have any number of invoices and an invoice belongs to at
most one customer. You create and end links with ordinary mutations, and
Bijection refuses any transaction whose final state breaks the declared bounds.

## Tables, views and link types

| | Table | View | Link type |
| - | - | - | - |
| Declared with | `defineTable` | `defineView` | `defineTable(...).link(...)` |
| Written by mutations | Yes | No | Yes |
| Row identity | Its own `_id` | Its key table's `_id` | Its own `_id` |
| Read with `ctx.db` | Yes | Yes | Yes |
| Reactive in queries | Yes | Yes | Yes |

<CardGroup cols={2}>
  <Card title="Defining views" href="/views/defining-views">
    Declare a view with a key and a `q` expression.
  </Card>

  <Card title="Joins and aggregates" href="/views/joins-and-aggregates">
    Combine tables with equality joins and group with counts and sums.
  </Card>

  <Card title="Reading views" href="/views/reading-views">
    Read views from queries, with indexes, pagination and reactivity.
  </Card>

  <Card title="Materialized views" href="/views/materialized-views">
    Store a view's output and keep it current on every write.
  </Card>

  <Card title="Link types" href="/views/links">
    Declare associations between records with enforced cardinality.
  </Card>
</CardGroup>

To control who can read a view or a link table, see
[Access rules](/access/overview).
