Skip to content

drewalth/pocketbase-swift-sdk

Repository files navigation

Pocketbase Swift SDK

A Swift SDK for Pocketbase v0.32.0.

Build Swift Platforms

Features

  • âś… Generic user authentication (supports any model)
  • âś… User authentication (sign up, sign in, sign out)
  • âś… Token refresh
  • âś… Password reset
  • âś… CRUD operations
  • âś… Realtime subscriptions
  • âś… Type-safe data models
  • âś… Automatic token management (stores tokens securely)

Note: This is a work in progress. The SDK is not yet have 100% feature parity with the js-sdk but it's close. Any contributions are welcome!

Motivation

Pocketbase is a great framework for quick prototyping and small scale applications. In an ideal world, Pocketbase would generate Swagger/OpenAPI documentation, but currently it doesn't. And there is no plan to add it in the future.

This SDK aims to make it easier to use Pocketbase with Swift projects.

Installation

Add the package to your Package.swift:

dependencies: [
    .package(url: "https://github.com/drewalth/pocketbase-swift-sdk.git", from: "1.0.0")
],
targets: [
    .target(name: "YourApp", dependencies: [.product(name: "PocketBase", package: "pocketbase-swift-sdk")])
]

Usage

SwiftUI

// Add the PocketBase instance to the environment
private struct PB: EnvironmentKey {
  static let defaultValue = PocketBase(baseURL: "http://127.0.0.1:8090")
}

extension EnvironmentValues {
  var pocketBase: PocketBase {
    get {
      self[PB.self]
    } set {
      self[PB.self] = newValue
    }
  }
}

// Access the PocketBase instance in your views
struct ContentView: View {
    @Environment(\.pocketBase) var pocketBase
    @State private var posts: [Post] = []

    var body: some View {
        List(posts, id: \.id) { post in
            Text(post.title)
        }
        .task {
            do {
                let postCollection: Collection<Post> = pocketBase.collection("posts")
                let postResult = try await postCollection.getList()
                posts = postResult.items
            } catch {
                print(error)
            }
        }
    }
}

See the example project for a more complete example.

Data Models

The SDK uses generics to support any user model that conforms to PBIdentifiableCollection. You need to define your own User models:

// Define your User model
struct User: PBIdentifiableCollection {
    let id: String
    let email: String
    let username: String?
    let name: String?
    let avatar: String?
    let verified: Bool
    let created: String
    let updated: String
    let collectionId: String
    let collectionName: String
    // Add any additional fields your PocketBase user collection has
}

Authentication

The SDK provides comprehensive authentication functionality with generic support:

User Authentication

// Sign up a new user
let createUserDto = CreateUser(
    email: "new@drewalth.com",
    name: "Test User",
    password: "password123",
    passwordConfirm: "password123")
let authResult = try await pb.signUp(dto: createUserDto, userType: User.self)

// Sign in with email/username and password
let authResult = try await pb.authWithPassword(
    email: "user@example.com",
    password: "password123",
    userType: User.self
)

// Refresh authentication token
let refreshResult = try await pb.authRefresh(userType: User.self)

// Check authentication status
if pb.isAuthenticated {
    print("User is authenticated")
    print("User ID: \(pb.currentUserId ?? "Unknown")")
}

// Sign out
pb.signOut()

Password Reset and Email Verification

// Request password reset
try await pb.requestPasswordReset(email: "user@example.com")

// Confirm password reset (with token from email)
try await pb.confirmPasswordReset(
    token: "reset_token_from_email",
    password: "newpassword",
    passwordConfirm: "newpassword"
)

// Request email verification
try await pb.requestVerification(email: "user@example.com")

// Confirm email verification (with token from email)
try await pb.confirmVerification(token: "verification_token_from_email")

CRUD Operations

let bookmarksCollection = pb.collection("bookmarks")

// Get a single record
let bookmark = try await bookmarksCollection.getOne(id: "e849z3g13jls740")

// Get a list of records
let bookmarks = try await bookmarksCollection.getList()

// Create a new record
let newBookmark = try await bookmarksCollection.create(record: Bookmark(
    title: "My First Bookmark",
    url: "https://www.google.com"
))

// Update a record
let updatedBookmark = try await bookmarksCollection.update(
    id: newBookmark.id,
    record: Bookmark(
        title: "My Updated Bookmark",
        url: "https://www.google.com"
    )
)

// Delete a record
try await bookmarksCollection.delete(id: newBookmark.id)

Expand Functionality

The SDK provides powerful expand functionality to include related data in your queries:

Basic Expansion

// Expand a single field
let expandQuery = ExpandQuery("author")
let posts = try await pb.getList(
    collection: "posts",
    model: Post.self,
    expand: expandQuery
)

// Expand multiple fields
let expandQuery = ExpandQuery("author", "category", "tags")
let posts = try await pb.getList(
    collection: "posts",
    model: Post.self,
    expand: expandQuery
)

Nested Expansion

// Expand nested relationships
let expandQuery = ExpandQuery("author.profile", "category.parent")
let posts = try await pb.getList(
    collection: "posts",
    model: Post.self,
    expand: expandQuery
)

Builder Pattern

// Use the builder pattern for complex expansions
let expandQuery = ExpandBuilder()
    .field("author")
    .field("category")
    .nested("author.profile")
    .nested("category.parent")
    .build()

let posts = try await pb.getList(
    collection: "posts",
    model: Post.self,
    expand: expandQuery
)

Fluent API with Expand

let posts: Collection<Post> = pb.collection("posts")

// Expand with fluent API
let expandQuery = ExpandQuery("author", "category")
let result = try await posts.getList(expand: expandQuery)

// Expand on single record
let post = try await posts.getOne(
    id: "post-id",
    expand: ExpandQuery("author.profile")
)

Conditional Expansion

let builder = ExpandBuilder()

if shouldIncludeAuthor {
    builder.field("author")
}

if shouldIncludeCategory {
    builder.field("category")
}

let expandQuery = builder.build()
let posts = try await pb.getList(
    collection: "posts",
    model: Post.self,
    expand: expandQuery
)

Realtime

let realtime = pb.realtime(
    collection: "bookmarks",
    record: "*",
    onConnect: {
        print("Connected to realtime")
    },
    onDisconnect: {
        print("Disconnected from realtime")
    },
    onEvent: { event in
        print("Received event: \(event.action) for record: \(event.record)")
    }
)

try await realtime.subscribe()

// Unsubscribe from realtime
realtime.unsubscribe()

Data Models

Define your data models by conforming to PBCollection:

struct Bookmark: PBCollection {
    let id: String
    let title: String
    let url: String
    let created: String
    let updated: String
    let collectionId: String
    let collectionName: String
}

For models that need an id property (like User and Admin), use PBIdentifiableCollection:

struct User: PBIdentifiableCollection {
    let id: String
    let email: String
    // ... other properties
}

About

The unofficial PocketBase Swift SDK

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Languages