Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.prism.byescaleira.com/llms.txt

Use this file to discover all available pages before exploring further.

Relationships

Prism supports three relationship types between database models: hasMany, belongsTo, and hasOne. Define them on your models, then load related data with simple queries.

Relationship Types

hasMany

A user has many posts. The foreign key lives on the related table.

belongsTo

A post belongs to a user. The foreign key lives on this table.

hasOne

A user has one profile. Like hasMany but returns a single row.

Defining Relationships

Schema
// users table
// id | name | email

// posts table
// id | user_id | title | body

// profiles table
// id | user_id | bio | avatar_url
User Model with Relationships
struct User: PrismRelatable {
    static var tableName: String { "users" }

    static var relations: [String: PrismRelation] {
        [
            "posts": .hasMany("posts", foreignKey: "user_id"),
            "profile": .hasOne("profiles", foreignKey: "user_id")
        ]
    }

    let id: Int
    let name: String
    let email: String

    init(row: PrismRow) {
        self.id = row["id"]?.intValue ?? 0
        self.name = row["name"]?.textValue ?? ""
        self.email = row["email"]?.textValue ?? ""
    }

    func toValues() -> [String: PrismDatabaseValue] {
        ["name": .text(name), "email": .text(email)]
    }
}
Post Model
struct Post: PrismRelatable {
    static var tableName: String { "posts" }

    static var relations: [String: PrismRelation] {
        [
            "author": .belongsTo("users", foreignKey: "user_id")
        ]
    }

    let id: Int
    let userId: Int
    let title: String

    init(row: PrismRow) {
        self.id = row["id"]?.intValue ?? 0
        self.userId = row["user_id"]?.intValue ?? 0
        self.title = row["title"]?.textValue ?? ""
    }

    func toValues() -> [String: PrismDatabaseValue] {
        ["user_id": .integer(userId), "title": .text(title)]
    }
}

Has Many

Load User's Posts
let user = try await db.find(User.self, id: 1)!
let posts = try await db.loadHasMany("posts", foreignKey: "user_id", localValue: "\(user.id)")

for post in posts {
    print(post["title"]?.textValue ?? "")
}

Belongs To

Load Post's Author
let post = try await db.find(Post.self, id: 1)!
let author = try await db.loadBelongsTo("users", primaryKey: "id", foreignValue: "\(post.userId)")

if let author {
    print("Author: \(author["name"]?.textValue ?? "")")
}

Has One

Load User's Profile
let profile = try await db.loadHasOne("profiles", foreignKey: "user_id", localValue: "\(user.id)")

if let profile {
    print("Bio: \(profile["bio"]?.textValue ?? "")")
}

API Example

Build an endpoint that returns users with their posts:
Users with Posts API
await server.get("/users/:id") { request in
    guard let id = Int(request.parameters["id"] ?? "") else {
        return .json(["error": "Invalid ID"], status: .badRequest)
    }

    guard let user = try await db.find(User.self, id: id) else {
        return .json(["error": "Not found"], status: .notFound)
    }

    let posts = try await db.loadHasMany("posts", foreignKey: "user_id", localValue: "\(user.id)")
    let profile = try await db.loadHasOne("profiles", foreignKey: "user_id", localValue: "\(user.id)")

    return .json([
        "id": user.id,
        "name": user.name,
        "email": user.email,
        "bio": profile?["bio"]?.textValue ?? "",
        "posts": posts.map { row in
            [
                "id": row["id"]?.intValue ?? 0,
                "title": row["title"]?.textValue ?? ""
            ] as [String: Any]
        }
    ] as [String: Any])
}
For performance-sensitive endpoints, consider using a single JOIN query instead of multiple relationship loads. Relationships are convenient for simple cases; raw SQL with JOINs is better when you need to minimize round trips.