This guide explains how to use libsqlite in practice, from first deployment to daily operations.
You are running a self-hosted control plane that manages:
- SQLite files on disk
- existing
.dbfiles imported from your server .dbfiles discovered from a mounted directory- remote libsql databases registered by URL and token
The control plane stores metadata, permissions, sessions, audit logs and migration history.
- Copy
backend/.env.exampletobackend/.env. - Set
MASTER_KEYwith 64 hex characters. - Set the storage and discovery paths.
- Start the backend.
Example:
cd backend
npm install
npm run build
npm run start- Copy
frontend/.env.exampletofrontend/.env. - Set
NEXT_PUBLIC_API_URL. - Start the frontend.
Example:
cd frontend
npm install
npm run build
npm run startThe backend exposes:
POST /api/v1/auth/register
Create the first user by sending email and password.
Example:
curl -X POST http://localhost:3000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"StrongPassword123!"}'The response returns:
- user information
accessTokenrefreshToken
Save the access token in the frontend or in your client.
Use the same endpoint for normal authentication:
POST /api/v1/auth/login
Example:
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"StrongPassword123!"}'Projects are the ownership boundary for databases.
Endpoint:
POST /api/v1/projects
Example:
curl -X POST http://localhost:3000/api/v1/projects \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{"name":"Billing"}'Why projects matter:
- keep databases grouped by application or client
- simplify permissions
- simplify backup and filesystem layout
Endpoint:
POST /api/v1/databases
Example:
curl -X POST http://localhost:3000/api/v1/databases \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"projectId":"<projectId>",
"name":"billing-db",
"type":"sqlite"
}'What happens:
- the control plane creates a metadata record
- a file is created in the structured storage tree
- a subdomain identifier is generated
- the database becomes active
Managed path example:
data/sqlite/projects/<projectId>/databases/<databaseId>.db
Use this when you already have a .db file on the server.
Endpoint:
POST /api/v1/databases/import-sqlite
Example:
curl -X POST http://localhost:3000/api/v1/databases/import-sqlite \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"projectId":"<projectId>",
"name":"legacy-db",
"sourcePath":"/srv/uploads/legacy.db"
}'What happens:
- the file is copied into managed storage
- the database is registered in metadata
- the file is now managed like any other database
Use this when Coolify or your server mounts a folder containing SQLite files.
Configure:
SQLITE_DISCOVERY_PATHSQLITE_DISCOVERY_PROJECT_ID- optional
SQLITE_DISCOVERY_ADOPT=true
Endpoint:
POST /api/v1/discovery/sqlite/scan
Example:
curl -X POST http://localhost:3000/api/v1/discovery/sqlite/scan \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"projectId":"<projectId>",
"rootPath":"/app/data/sqlite",
"adopt":false
}'Two modes exist:
- records the file in metadata
- keeps the file where it already is
- copies the file into the managed storage tree
- keeps all managed databases in one consistent hierarchy
Endpoint for adoption:
POST /api/v1/discovery/sqlite/adopt
Example:
curl -X POST http://localhost:3000/api/v1/discovery/sqlite/adopt \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"projectId":"<projectId>",
"rootPath":"/app/data/sqlite"
}'Use this for a remote libsql or Turso-compatible endpoint.
Endpoint:
POST /api/v1/provisioning/libsql
Example:
curl -X POST http://localhost:3000/api/v1/provisioning/libsql \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"projectId":"<projectId>",
"name":"remote-app-db",
"url":"libsql://example.turso.io",
"token":"<libsql-token>"
}'Endpoint:
GET /api/v1/databases/:id/schema
This shows:
- table names
- columns
- foreign keys
Endpoint:
POST /api/v1/databases/:id/query
Example:
curl -X POST http://localhost:3000/api/v1/databases/<dbId>/query \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"sql":"SELECT * FROM users LIMIT 10",
"params":[]
}'Rules:
- read queries are allowed for SQLite through the read path
- writes are allowed through the managed API with permission checks
Endpoint:
POST /api/v1/databases/:id/migrations
Example:
curl -X POST http://localhost:3000/api/v1/databases/<dbId>/migrations \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{
"name":"create-users-table",
"sql":"CREATE TABLE users (id TEXT PRIMARY KEY, email TEXT NOT NULL);"
}'This is the recommended way to run schema changes from your codebase or CI.
Use:
PATCH /api/v1/databases/:id/rotate-token
This is useful when:
- a credential may have been exposed
- a database is moving between environments
- you want to refresh access regularly
Use:
GET /api/v1/auditGET /healthGET /ready
These endpoints help you verify:
- what changed
- whether the API is alive
- whether the database layer is ready
- Create a project.
- Create a SQLite database or import an existing one.
- Or register a libsql endpoint.
- Apply migrations from your code or CI.
- Inspect schema and run queries when needed.
- Use the audit log for traceability.
- Backup the control-plane database and managed SQLite files.
- forgetting to set
MASTER_KEY - mounting a directory but not setting
SQLITE_DISCOVERY_PATH - trying to manage a remote database without registering URL and token
- assuming the backend can guess arbitrary files on another machine