Models
A Keel app is built around your data, which is described using models. Models are defined in your schema files and typically look something like this.
model Product {
fields {
title Text
description Text
price Number
onSale Boolean
}
}
This example defines a model called Product
which has a number of fields, which are defined inside a fields
block within the model definition. Each field has a name and a type. The type can be any of the built-in Keel types or one you’ve defined elsewhere in your schema, for example an enum or another model.
All models have a few built-in fields by default - id
, createdAt
, and updatedAt
, So, even though it's not a common practice, it's valid to define a model without a fields block. This means the following is a valid model.
model Product {}
Naming
Keel enforces certain naming conventions to ensure consistency in your schema and API’s. Specifically, your models must be named in UpperCamelCase, and your fields must be named in lowerCamelCase.
We also recommend that you name your models using singular terms, such as Product
rather than Products
.
Field Types
The basic built-in Keel types are:
Text
- suitable for any text data of any lengthNumber
- a whole number, positive or negativeBoolean
- a true or false valueTimestamp
- a date time accurate to a microsecondDate
- a date, with no timeID
- basically the same asText
but indicates the value is used to identify something
As previously mentioned, all models automatically have the following fields:
id
- a field of typeID
that can be used to identify an individual recordcreatedAt
- a field of typeTimestamp
which indicates when a record was createdupdatedAt
- a field of typeTimestamp
which indicates when a record was last updated
You don't have to manually set or update these fields as they all have default values which are filled in automatically when you create or update a record.
Enums
An enum is a set of named values. They are useful when you want to constrain a field to only contain certain values. For example if you are defining a model for an order and want to make sure that the status of the order can only be placed, picked, packed or dispatched, then you can do that with an enum.
enum OrderStatus {
Placed
Picked
Packed
Dispatched
}
model Order {
fields {
status OrderStatus
}
}
Now you can be sure that the status
field of an order can only be one of the values listed in the enum.
Repeated Fields
Some fields can be marked as repeated, meaning they contain a list of values rather than a single value. To mark a field as repeated, add []
after its type. A field can be repeated if its type is Text
, Number
, an enum, or another model.
model Product {
fields {
title Text
tags Text[]
}
}
Unique Fields
Sometimes a field needs to be unique across all instances of a model. As an example every book has an International Standard Book Number (ISBN), which is unique to each book. To model this in a Keel schema you can use a Text
field with the @unique
attribute.
model Book {
fields {
isbn Text @unique
}
}
Now we can be sure that no two books will have the same ISBN.
Composite Unique Fields
In your model, you can also set a combination of fields to be unique for each instance.
model Car {
fields {
vin Text
licensePlateNumber Text
}
@unique([vin, licensePlateNumber])
}
Now we can be sure that no two cars will have the same VIN and Licence Plate Number.
Default values
Previously, we discussed how models built-in fields have default values that are used when a record is created. You can also add default values to your own fields using the @default
attribute. This attribute accepts a single argument, which is the value you want to use as the default
For example, the following schema specifies that if no value is provided for the status
field when an order is created, then the default value Placed
should be used.
model Order {
fields {
status OrderStatus @default(OrderStatus.Placed)
}
}
The value you provide to the @default
attribute must match the type of the field, otherwise you will get a schema validation error.
Relationships
Data in a Keel app is relational, which means that models can be related to one another. This is expressed in a Keel schema by using a model as a field type.
One to many relationships
One to many relationships are very common, and a simple example is the idea of an order which has many items. To model this in a Keel schema you would add fields to both the OrderItem
and Order
model that reference each other.
model Order {
fields {
items OrderItem[]
}
}
model OrderItem {
fields {
order Order
}
}
Because an order has many items, the items
field in the Order
model is repeated (indicated by []
after the model name), whereas an item belongs to a single order, so the order
field in the OrderItem
model is not repeated. In one-to-many relationships the belongs to side must be defined, whereas the has many side is optional.
Many to many relationships
Another common type of relationship in data modelling is many to many. Many to many relationships are a little bit more complex than one to many relationships as they require an intermediary model. The intermediary model is just a normal model that is used to join the two models that should have a many-to-many relationship together.
A simple example of a many to many relationship is products and tags, where a product can have many tags and a tag can be applied to many products. This would be modelled in a Keel schema using the following three models.
model Product {
fields {
tags ProductTags[]
}
}
model Tag {
fields {
products ProductTags[]
}
}
model ProductTag {
fields {
product Product
tag Tag
}
}
The models Product
and Tag
don’t directly reference each other in this schema, that is done in the ProductTag
model, which references both. In fact, even though we would talk about this as a many-to-many relationship, it’s implemented using two one to many relationships.
As the join model is just a normal model, it has all the normal built-in model fields. This means you can reference a specific product/tag relationship by its id
field and know when it was created by using the createdAt
field.
You can add any fields you like to these join models, which can be useful for storing additional properties about the relationship. For example if you are modelling team members and projects, where a project is worked on by many team members and team members can be part of many projects, you might also want to store what role each team member takes in each project. The join model would be a good place to store this information.
enum TeamMemberRole {
Lead
Member
Observer
}
model TeamMember {}
model Project {}
model ProjectTeamMember {
fields {
teamMember TeamMember
project Project
role TeamMemberRole
}
}
One to one relationships
A one-to-one relationship is a relationship in which neither side is repeated, and one side uses the @unique
attribute. For example, in the following schema, a country can have only one capital city, and a city can be in only one country.
model Country {
fields {
capitalCity City
}
}
model City {
fields {
country Country @unique
}
}