Relationships

Most mock servers hand you disconnected data. Akuma understands relationships, so a post's author is a real author that actually exists in the database.

Reference a schema as a type

Give a property the type of another schema and Akuma creates a relationship. It adds a foreign key column and seeds it with valid IDs from the referenced table.

yaml
schemas:
  Author:
    properties:
      id: number
      name: string
      email: string

  Post:
    properties:
      id: number
      title: string
      body: string
      author: Author      # creates authorId -> authors.id

What Akuma does

  • Foreign key column. The author property becomes an INTEGER column named authorId (the property name in the configured style, camelCase by default).
  • References the right table. authorId points at authors.id, the referenced schema's table.
  • Valid seed data. Every seeded post's authorId is a real author that exists in the authors table, never a random number.
  • Enforced. The column is created with a real FOREIGN KEY constraint, enforced by SQLite.
sql
CREATE TABLE posts (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT,
  body TEXT,
  authorId INTEGER,
  FOREIGN KEY (authorId) REFERENCES authors(id)
);

Foreign key naming

By default a foreign key is camelCase (authorId). Set naming.foreignKeys to match your house style. This affects only the generated FK columns; your other columns keep the names you write.

yaml
naming:
  foreignKeys: snake_case    # camelCase (default) | snake_case | PascalCase
Styleauthor: Author becomes
camelCase (default)authorId
snake_caseauthor_id
PascalCaseAuthorId

Tip: tables are always snake_case (line_items), so snake_case foreign keys give a fully snake_case database.

Seeding order

Akuma seeds tables in dependency order: referenced tables first. Authors are created and seeded before posts, so by the time a post needs an authorId, real authors already exist to point at.

shell
curl http://localhost:3000/posts
# => [{"id": 1, "title": "Example 1", "authorId": 2}, ...]

curl http://localhost:3000/authors/2
# => {"id": 2, "name": "Example 2", ...}   <- authorId 2 is real

A complete example

Authors, posts, and comments: two relationships, fully wired:

yaml
schemas:
  Author:
    properties:
      id: number
      name: string

  Post:
    properties:
      id: number
      title: string
      author: Author      # postId-side FK: authorId -> authors.id

  Comment:
    properties:
      id: number
      post: Post          # creates postId -> posts.id
      text: string

endpoints:
  - path: /authors
    method: GET
    response:
      type: array
      schema: Author

  - path: /posts
    method: GET
    response:
      type: array
      schema: Post

  - path: /comments
    method: GET
    response:
      type: array
      schema: Comment

Notes

  • The foreign key column is named <property>Id. A property already ending in Id is left as-is (no authorIdId).
  • A relationship is detected when a property's type exactly matches a schema name. A plain author: string stays a plain text column.
  • Relationship cycles (A references B references A) are seeded best-effort, with some foreign keys left null; Akuma warns when it detects one.

See Data models for schema basics and Endpoints for serving them.

Common mistakes

A relationship column collides with a property

A relationship already adds an id column, so author: Author creates authorId. Declaring a separate authorId property collides, and Akuma refuses to start:

output
Error: Schema 'Post' produces a duplicate column 'authorId' from properties 'author' and 'authorId'. Relationship properties get an 'Id' suffix (e.g. `author` -> `authorId`), so rename one of them.

A typo in the type silently becomes text

A relationship is detected only when the type exactly matches a schema name. A typo (author: Auther) is not a relationship; it becomes a plain text column, with a warning:

output
Schema 'Post' property 'author' has unknown type 'Auther'; treating it as text.