Turso Goes Mobile With Official iOS & Android SDKs

Glauber CostaGlauber Costa
Cover image for Turso Goes Mobile With Official iOS & Android SDKs

Turso is built on our fork of SQLite, adding key features like native Vector Search for AI, and encryption at rest. Until now, we have focused on server workloads, specifically in the four languages for which we have official SDKs:

  • TypeScript
  • Go
  • Rust
  • Python

Today we’re adding even more official SDKs, and updating existing ones for Mobile.

In this article, we’ll be talking about our official Swift SDK for iOS, our official Android SDK with Kotlin, and our official React Native SDK for cross-platform mobile development.

You'll also learn further down about how we actually built these SDKs using C, as well as updates to the PHP SDK, and a first-look at our new Ruby SDK.

#Mobile

If you have done any meaningful work on mobile devices, there is certainly a database you are familiar with – SQLite. Now you can Turbo charge this with Turso’s unique capabilities on your mobile devices.

See how mobile AI app Kin used on-device vector search for iOS and Android in this case study.

By taking advantage of our embedded replicas, data can be replicated straight into your user’s mobile device. With Turso’s multitenant databases, data can be partitioned per-user, so that only the user’s database is synced into the device.

There are two officially supported mobile SDKs – iOS and Android.

#iOS (Swift)

Whether you’re building for macOS, iOS, iPad, tvOS, or watchOS, the new official Swift SDK is perfect for building fully native AI powered applications.

Using Embedded Replicas, and libSQL Vector Search means you can build fully native apps offline — even apps that live on your wrist!

Once you’ve installed the SwiftPM dependency, you can execute SQL, and sync changes from your remote Turso database at the desired interval:

import Libsql

let db = try Database(
    path: "./local.db",
    url: "TURSO_DATABASE_URL",
    authToken: "TURSO_AUTH_TOKEN",
    syncInterval: 1000
)

let conn = try db.connect()

try conn.execute("
  CREATE TABLE IF NOT EXISTS todos (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      description TEXT
  );
  INSERT INTO todos (name) VALUES ('Launch Week 3');
")

try conn.query("SELECT * FROM todos WHERE id = ?", [1])

#Android (Kotlin)

Our commitment to mobile doesn’t stop at iOS. Android developers, we’ve got you covered too!

With our new Android SDK, you can now harness the power of Turso’s Vector Search, online or offline with Embedded Replicas:

import tech.turso.libsql.Libsql

val db = Libsql.open(
    path = "./local.db",
    url = "TURSO_DATABASE_URL",
    authToken = "TURSO_AUTH_TOKEN",
    syncInterval = 1000
)

db.connect().use {
    it.execute_batch("""
        CREATE TABLE IF NOT EXISTS movies (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT,
            year INTEGER,
            embedding F32_BLOB(4)
        );

        CREATE INDEX movies_idx ON movies(libsql_vector_idx(embedding));

        INSERT INTO movies (title, year, embedding) VALUES
        ('Halloween', 1978, vector32('[0.900, 0.750, 0.600, 0.800]')),
        ('The Nightmare Before Christmas', 1993, vector32('[0.700, 0.800, 0.900, 0.600]')),
        ('Hocus Pocus', 1993, vector32('[0.500, 0.700, 0.800, 0.400]')),
        ('Ghostbusters', 1984, vector32('[0.600, 0.500, 0.700, 0.300]')),
        ('The Addams Family', 1991, vector32('[0.400, 0.600, 0.500, 0.200]'));
    """)
}

fun Database.findSimilarMovies(queryVector: String, limit: Int = 3) {
    this.connect().use { conn ->
        conn.query(
            """
                SELECT title
                FROM vector_top_k('movies_idx', vector32(?), ?)
                JOIN movies ON movies.rowid = id
            """,
            queryVector,
            limit
        ).map { row ->
            Pair(row[0] as String, row[1] as Long)
        }
    }
}

Once you’re ready to fetch similar spooky movies, you can use the native libSQL Vector Similarity Search:

val queryVector = "[0.800, 0.700, 0.750, 0.600]"
val similarMovies = db.findSimilarMovies(queryVector)

println("Top 3 similar spooky movies:")
similarMovies.forEach { (title, year) ->
    println("$title ($year)")
}

#React Native and Flutter

We’re also incredibly excited to share two community libSQL SDKs for React Native, and Flutter.

OP-SQLite provides a low-level API (WebSQL) to execute SQL queries on React Native that has built-in support for libSQL. You can use OP-SQLite with a remote Turso Database:

import { openRemote } from '@op-engineering/op-sqlite';

const remoteDb = openRemote({
  url: ‘...’,
  authToken: ‘...’
});

OP-SQLite also supports Embedded Replicas whereby your local SQLite file is updated from the remote Turso Database:

import { openSync } from '@op-engineering/op-sqlite';

const remoteDb = openSync({
  name: 'local.db',
  url: '...',
  authToken: '...',
});

For those building with Flutter, the community SDK is now part of the Turso Documentation, and also works with the Turso features you love:

final dir = await getApplicationCacheDirectory();
final path = '${dir.path}/local.db';

final client = LibsqlClient(path)
  ..authToken = '<TOKEN>'
  ..syncUrl = '<TURSO_OR_LIBSQL_URL>'
  ..syncIntervalSeconds = 5
  ..readYourWrites = true;

#More love on the server too!

The mobile device enablement does not constitute a change of direction, and we still have new updates for the server-side crowd.

#PHP

We’ve been working with the Lambo crowd for the last year to find a good developer experience for using and working with libSQL and PHP.

Today, we have a brand new official PHP driver for you to try.

composer require turso/libsql

The PHP driver supports syncing from a remote database to a local SQLite file with Embedded Replicas out of the box, and it’s fully compatible with libSQL Vector Search:

<?php

$db = new Database(
    path: 'local.db',
    url: getenv('TURSO_DATABASE_URL'),
    authToken: getenv('TURSO_AUTH_TOKEN')
    syncInterval: 1000
);

$conn = $db->connect();

$conn->executeBatch("
    CREATE TABLE IF NOT EXISTS movies (
        title TEXT,
        year INT,
        embedding F32_BLOB(4)  -- 4-dimensional f32 vector representing spookiness
    );

    CREATE INDEX movies_idx ON movies(libsql_vector_idx(embedding));
");

Now, let’s insert movies into our table, including their spooky vector embeddings:

$conn->execute("
    INSERT INTO movies (title, year, embedding) VALUES
    ('Halloween', 1978, vector32('[0.900, 0.750, 0.600, 0.800]')),
    ('The Nightmare Before Christmas', 1993, vector32('[0.700, 0.800, 0.900, 0.600]')),
    ('Hocus Pocus', 1993, vector32('[0.500, 0.700, 0.800, 0.400]')),
    ('Ghostbusters', 1984, vector32('[0.600, 0.500, 0.700, 0.300]')),
    ('The Addams Family', 1991, vector32('[0.400, 0.600, 0.500, 0.200]'))
");

When you’re ready to perform a vector similarity search to find the most similar movies based on their spookiness, you can do that natively with SQLite:

$query = "
    SELECT title, year
    FROM vector_top_k('halloween_movies_idx', vector32('[0.800, 0.700, 0.750, 0.600]'), 3)
    JOIN halloween_movies ON halloween_movies.rowid = id
";

$result = $conn->query($query);

echo "Top 3 Halloween movies with similar spookiness:\n";
while ($row = $result->fetchArray()) {
    echo "Title: " . $row['title'] . ", Year: " . $row['year'] . "\n";
}

This query uses the vector_top_k function to efficiently find the top 3 movies with spookiness most similar to the vector [0.800, 0.700, 0.750, 0.600], which represents a pretty spooky movie!

#Ruby

Today we also share the first look of the upcoming libsql-ruby SDK available now on RubyGems. The Ruby SDK will go on to support a new activerecord-libsql adapter, coming soon for the Rails ecosystem.

gem install turso_libsql

The Ruby SDK supports all of the Turso features, including Encryption at Rest, Embedded Replicas, and native Vector Search:

db = Libsql::Database.new(path: 'local.db')

db.connect do |conn|
  conn.execute_batch <<-SQL
    DROP TABLE IF EXISTS movies;
    CREATE TABLE IF NOT EXISTS movies (title TEXT, year INT, embedding F32_BLOB(3));
    CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding));

    INSERT INTO movies (title, year, embedding)
    VALUES
      ('Halloween', 1978, vector32('[0.900, 0.750, 0.600, 0.800]')),
      ('The Nightmare Before Christmas', 1993, vector32('[0.700, 0.800, 0.900, 0.600]')),
      ('Hocus Pocus', 1993, vector32('[0.500, 0.700, 0.800, 0.400]')),
      ('Ghostbusters', 1984, vector32('[0.600, 0.500, 0.700, 0.300]')),
      ('The Addams Family', 1991, vector32('[0.400, 0.600, 0.500, 0.200]'));
  SQL

  rows = conn.query "SELECT title, year FROM vector_top_k('movies_idx', '[4,5,6]', 3) JOIN movies ON movies.rowid = id"

  print "Movies: #{rows.to_a}\n"

  rows.close
end

#C (FFI)

To make possible the creation of all SDKs announced today, we bridged the gap between our core Rust implementation and high-level languages by creating a robust Foreign Function Interface (FFI) layer.

This FFI layer enables building SDKs for both interpreted and native languages, and it’s a lot more manageable than what we did previously.

For developers who need low-level access, or want to build a community SDK – libsql-c is now available.

#include "libsql.h"

int main() {
    libsql_database_t db = libsql_database_init((libsql_database_desc_t){
            .path = "local.db",
            .url = "TURSO_DATABASE_URL",
            .auth_token = "TURSO_AUTH_TOKEN",
            .sync_interval = 60000
        });

    libsql_connection_t conn = libsql_database_connect(db);

    const char* query =
        "SELECT title, year FROM vector_top_k('movies_idx', vector32('[0.800, 0.700, 0.750, 0.600]'), 3) "
        "JOIN movies ON movies.rowid = id;";

    libsql_batch_t batch = libsql_connection_batch(conn, setup_sql);

    stmt = libsql_prepare(conn, query);

    while (libsql_step(stmt) == LIBSQL_ROW) {
        const char* title = libsql_column_text(stmt, 0);
        int year = libsql_column_int(stmt, 1);
        printf("%s (%d)\n", title, year);
    }
}

#Conclusion

With today’s announcements, developers can build sophisticated AI-powered applications, on mobile for iOS and Android with offline reads using Embedded Replicas.

We’re actively making changes to existing SDKs for TypeScript, Go, and Python to use our new workflow while ensuring backwards compatibility.

Get Started Today — we’re excited to see what you build!

scarf