In Spooky, you define your data model using sql schema files. This approach serves as the single source of truth for your application, from which TypeScript interfaces are automatically generated.
Defining Your Schema
You start by creating .surql files in your schema directory. Spooky advocates for a “Schema First” approach, ensuring your data structure is explicit, typesafe, and secure.
Tables and Fields
Here is a simple example of how to define a user table. Note the use of DEFINE TABLE and DEFINE FIELD to enforce data integrity.
sql
-- Define the user table with full schema enforcementDEFINE TABLE user SCHEMAFULLPERMISSIONS FOR select, create WHERE true FOR update, delete WHERE id = $auth.id; -- Only the user can modify their own record-- Define fields with strict types and assertionsDEFINE FIELD username ON TABLE user TYPE stringASSERT $value != NONE AND string::len($value) > 3;DEFINE INDEX unique_username ON TABLE user FIELDS username UNIQUE;DEFINE FIELD created_at ON TABLE user TYPE datetimeVALUE time::now();
Relationships
SurrealDB shines with its graph capabilities. Relationships are first-class citizens and can be modeled in several ways depending on your needs.
One-to-One (1:1)
For a one-to-one relationship, such as a User having a single Profile, you can simply use a record<table_name> type.
sql
DEFINE TABLE user SCHEMAFULL;DEFINE TABLE profile SCHEMAFULL;-- The user table has a link to a specific profileDEFINE FIELD profile ON TABLE user TYPE record<profile>;-- The profile can also link back to the user if neededDEFINE FIELD user ON TABLE profile TYPE record<user>;
One-to-Many (1:N)
Models like a Thread having many Comments are classic one-to-many relationships. You define this by putting a record<parent> field on the child table.
sql
DEFINE TABLE thread SCHEMAFULL;DEFINE TABLE comment SCHEMAFULL;-- Child (Comment) stores a reference to Parent (Thread)DEFINE FIELD thread ON TABLE comment TYPE record<thread>;-- You can also optionally store an array of references on the parent (not strictly required for querying)DEFINE FIELD comments ON TABLE thread TYPE array<record<comment>>;
Many-to-Many (N:M)
For complex relationships, like Users following other Users (graph), or Posts having Tags, use a Relation Table. This is a special table that stores the edges of the graph.
sql
-- 1. Define the component tablesDEFINE TABLE user SCHEMAFULL;DEFINE TABLE post SCHEMAFULL;-- 2. Define the edge (relation) table-- It connects a 'user' (from) to a 'post' (to) via a 'liked' edgeDEFINE TABLE liked SCHEMAFULL TYPE RELATIONFROM user TO post;-- 3. (Optional) Define properties on the edgeDEFINE FIELD created_at ON TABLE liked TYPE datetime VALUE time::now();-- 4. Create the relationship using standard sql or Events-- RELATE user:john->liked->post:surrealdb_is_cool;
Generated Types
Once your schema is defined, use the Spooky CLI to generate TypeScript definitions:
Bash
# Generate TypeScript typesspooky --input src/schema.surql --output schema.gen.ts --format typescript# Or generate all formats at oncespooky --input src/schema.surql --output schema --all
For example, the user table above might generate:
TypeScript
export type User = { id: RecordId<"user">; username: string; created_at: Date; // Relationships are typed as RecordIds profile?: RecordId<"profile">;};
These types are then available directly in your client application, ensuring that your frontend code is always in sync with your database schema.