SQLite Storage
- Granting SQLite Database Permissions to Components
- Using SQLite Storage From Applications
- Preparing an SQLite Database
- Custom SQLite Databases
Spin provides an interface for you to persist data in an SQLite database managed by Spin. This database allows Spin developers to persist relational data across application invocations.
		Why do I need a Spin interface? Why can't I just use my own external database?
		
			
		
	
You can absolutely still use your own external database either with the MySQL or Postgres APIs. However, if you’re interested in quick, local relational storage without any infrastructure set-up then Spin’s SQLite database is a great option.
Granting SQLite Database Permissions to Components
By default, a given component of an app will not have access to any SQLite databases. Access must be granted specifically to each component via the component manifest. For example, a component could be given access to the default store using:
[component]
sqlite_databases = ["default"]
Note: To deploy your Database application to Fermyon Cloud using
spin cloud deploy, see the SQLite Database section in the documentation. It covers signing up for the private beta and setting up your Cloud database tables and initial data.
Using SQLite Storage From Applications
The Spin SDK surfaces the Spin SQLite database interface to your language.
The set of operations is common across all SDKs:
| Operation | Parameters | Returns | Behavior | 
|---|---|---|---|
| open | name | connection | Open the database with the specified name. If nameis the string “default”, the default database is opened, provided that the component that was granted access in the component manifest fromspin.toml. Otherwise,namemust refer to a store defined and configured in a runtime configuration file supplied with the application. | 
| execute | connection, sql, parameters | database records | Executes the SQL statement and returns the results of the statement. SELECT statements typically return records or scalars. INSERT, UPDATE, and DELETE statements typically return empty result sets, but may return values in some cases. The executeoperation recognizes the SQLite dialect of SQL. | 
| close | connection | - | Close the specified connection. | 
The exact detail of calling these operations from your application depends on your language:
SQLite functions are available in the spin_sdk::sqlite module. The function names match the operations above. For example:
use anyhow::Result;
use serde::Serialize;
use spin_sdk::{
    http::{Request, Response},
    http_component,
    sqlite::{Connection, Error, ValueParam},
};
#[http_component]
fn handle_request(req: Request) -> Result<Response> {
    let connection = Connection::open_default()?;
    let execute_params = [
        ValueParam::Text("Try out Spin SQLite"),
        ValueParam::Text("Friday"),
    ];
    connection.execute(
        "INSERT INTO todos (description, due) VALUES (?, ?)",
        execute_params.as_slice(),
    )?;
    let rowset = connection.execute(
        "SELECT id, description, due FROM todos",
        &[]
    )?;
    let todos: Vec<_> = rowset.rows().map(|row|
        ToDo {
            id: row.get::<u32>("id").unwrap(),
            description: row.get::<&str>("description").unwrap().to_owned(),
            due: row.get::<&str>("due").unwrap().to_owned(),
        }
    ).collect();
    let body = serde_json::to_vec(&todos)?;
    Ok(http::Response::builder().status(200).body(Some(body.into()))?)
}
// Helper for returning the query results as JSON
#[derive(Serialize)]
struct ToDo {
    id: u32,
    description: String,
    due: String,
}
General Notes
- All functions are on the spin_sdk::sqlite::Connectiontype.
- Parameters are instances of the ValueParamenum; you must wrap raw values in this type.
- The executefunction returns aQueryResult. To iterate over the rows use therows()function. This returns an iterator; usecollect()if you want to load it all into a collection.
- The values in rows are instances of the ValueResultenum. However, you can userow.get(column_name)to extract a specific column from a row.getcasts the database value to the target Rust type. If the compiler can’t infer the target type, writerow.get::<&str>(column_name)(or whatever the desired type is).
- All functions wrap the return in Result, with the error type beingspin_sdk::sqlite::Error.
To use SQLite functions, use the Sqlite.open or Sqlite.openDefault function to obtain a Connection object. Connection provides the execute method as described above. For example:
import {Sqlite} from "@fermyon/spin-sdk"
const conn = Sqlite.openDefault();
const result = conn.execute("SELECT * FROM todos WHERE id > (?);", [1]);
const json = JSON.stringify(result.rows);
General Notes
- The spinSdkobject is always available at runtime. Code checking and completion are available in TypeScript at design time if the module imports anything from the@fermyon/spin-sdkpackage.
- Parameters are JavaScript values (numbers, strings, byte arrays, or nulls). Spin infers the underlying SQL type.
- The executefunction returns an object withrowsandcolumnsproperties.columnsis an array of strings representing column names.rowsis an array of rows, each of which is an array of JavaScript values (as above) in the same order ascolumns.
- The Connectionobject doesn’t surface theclosefunction.
- If a Spin SDK function fails, it throws an Error.
To use SQLite functions, use the spin_sqlite module in the Python SDK. The sqlite_open and sqlite_open_default functions return a connection object. The connection object provides the execute method as described above. For example:
from spin_http import Response
from spin_sqlite import sqlite_open_default
def handle_request(request):
    conn = sqlite_open_default()
    result = conn.execute("SELECT * FROM todos WHERE id > (?);", [1])
    rows = result.rows()
    return Response(200,
                    {"content-type": "application/json"},
                    bytes(str(rows), "utf-8"))
General Notes
- Parameters are Python values (numbers, strings, and lists). Spin infers the underlying SQL type.
- The executemethod returns an object withrowsandcolumnsmethods.columnsreturns a list of strings representing column names.rowsis an array of rows, each of which is an array of Python values (as above) in the same order ascolumns.
- The connection object doesn’t surface the closefunction.
- Errors are surfaced as exceptions.
The Go SDK is implemented as a driver for the standard library’s database/sql interface.
package main
import (
	"encoding/json"
	"net/http"
	spinhttp "github.com/fermyon/spin/sdk/go/http"
	"github.com/fermyon/spin/sdk/go/sqlite"
)
type Todo struct {
	ID          string
	Description string
	Due         string
}
func init() {
	spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) {
		db := sqlite.Open("default")
		defer db.Close()
		_, err := db.Exec("INSERT INTO todos (description, due) VALUES (?, ?)", "Try out Spin SQLite", "Friday")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		rows, err := db.Query("SELECT id, description, due FROM todos")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		var todos []*Todo
		for rows.Next() {
			var todo Todo
			if err := rows.Scan(&todo.ID, &todo.Description, &todo.Due); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			todos = append(todos, &todo)
		}
		json.NewEncoder(w).Encode(todos)
	})
}
func main() {}
General Notes
A convenience function sqlite.Open() is provided to create a database connection. Because the http.Handle function is inside the init() function the Spin SQLite driver cannot be initialized the same way as other drivers using sql.Open.
Preparing an SQLite Database
Although Spin provides SQLite as a built-in database, SQLite still needs you to create its tables.  In most cases, the most convenient way to do this is to use the spin up --sqlite option to run whatever SQL statements you need before your application starts.  This is typically used to create or alter tables, but can be used for whatever other maintenance or troubleshooting tasks you need.
You can run a SQL script from a file using the @filename syntax:
spin up --sqlite @migration.sql
Or you can pass SQL statements directly on the command line as a (quoted) string:
spin up --sqlite "CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY AUTOINCREMENT, description TEXT NOT NULL, due TEXT NOT NULL)"
As with runtime operations, this flag uses the SQLite dialect of SQL.
You can provide the --sqlite flag more than once; Spin runs the statements (or files) in the order you provide them, and waits for each to complete before running the next.
It’s also possible to create tables from your Wasm components using the usual
executefunction. That can end up mingling your “hot path” application logic with database maintenance code; decide which approach is best based on your application’s needs.
Custom SQLite Databases
Spin defines a database named "default" and provides automatic backing storage.  If you need to customize Spin with additional databases, or to change the backing storage for the default database, you can do so via the --runtime-config-file flag and the runtime-config.toml file.  See SQLite Database Runtime Configuration for details.
Granting Access to Custom SQLite Databases
As mentioned above, by default, a given component of an app will not have access to any SQLite databases. Access must be granted specifically to each component via the component manifest, using the component.sqlite_databases field in the manifest.
Components can be given access to different databases, and may be granted access to more than one database. For example:
# c1 has no access to any databases
[component]
name = "c1"
# c2 can use the default database, but no custom databases
[component]
name = "c2"
sqlite_databases = ["default"]
# c3 can use the custom databases "marketing" and "sales", which must be
# defined in the runtime config file, but cannot use the default database
[component]
name = "c3"
sqlite_databases = ["marketing", "sales"]
 
                             
                            