Skip to content

Database Operations

Backflow supports Supabase, Firebase, SQLite, and custom REST-based databases (MongoDB, BigQuery, etc.) with a unified query interface.

Configuration

json
{
  "databaseProvider": "supabase",
  "supabase": {
    "url": "{{env.SUPABASE_URL}}",
    "anonKey": "{{env.SUPABASE_ANON_KEY}}"
  }
}
json
{
  "databaseProvider": "firebase",
  "firebase": {
    "projectId": "{{env.FIREBASE_PROJECT_ID}}",
    "clientEmail": "{{env.FIREBASE_CLIENT_EMAIL}}",
    "privateKey": "{{env.FIREBASE_PRIVATE_KEY}}"
  }
}
json
{
  "databaseProvider": "sqlite",
  "sqlite": {
    "path": "./data/app.db"
  }
}

Query Structure

json
{
  "supabaseQueries": [{
    "table": "users",
    "operation": "select",
    "select": "id, name, email",
    "filters": [],
    "order": { "column": "created_at", "ascending": false },
    "limit": 10
  }]
}
json
{
  "firebaseQueries": [{
    "collection": "users",
    "operation": "query",
    "filters": [],
    "orderBy": { "field": "created_at", "direction": "desc" },
    "limit": 10
  }]
}
json
{
  "supabaseQueries": [{
    "table": "users",
    "operation": "select",
    "select": "id, name, email",
    "filters": [],
    "order": { "column": "created_at", "ascending": false },
    "limit": 10
  }]
}

SQLite uses the same query schema as Supabase.

Operations

Select / Query

json
{
  "table": "posts",
  "operation": "select",
  "select": "*",
  "single": false
}
json
{
  "collection": "posts",
  "operation": "query"
}
json
{
  "table": "posts",
  "operation": "select",
  "select": "*",
  "single": false
}

Get by ID

json
{
  "table": "posts",
  "operation": "select",
  "filters": [{ "column": "id", "operator": "eq", "value": "{{params.id}}" }],
  "single": true
}
json
{
  "collection": "posts",
  "operation": "get",
  "docId": "{{params.id}}"
}
json
{
  "table": "posts",
  "operation": "select",
  "filters": [{ "column": "id", "operator": "eq", "value": "{{params.id}}" }],
  "single": true
}

Insert / Add

json
{
  "table": "posts",
  "operation": "insert",
  "data": {
    "title": "{{body.title}}",
    "content": "{{body.content}}",
    "user_id": "{{auth.sub}}"
  },
  "select": "*",
  "single": true
}
json
{
  "collection": "posts",
  "operation": "add",
  "data": {
    "title": "{{body.title}}",
    "content": "{{body.content}}",
    "user_id": "{{auth.sub}}"
  }
}
json
{
  "table": "posts",
  "operation": "insert",
  "data": {
    "title": "{{body.title}}",
    "content": "{{body.content}}",
    "user_id": "{{auth.sub}}"
  },
  "select": "*",
  "single": true
}

Update

json
{
  "table": "posts",
  "operation": "update",
  "data": { "title": "{{body.title}}" },
  "filters": [{ "column": "id", "operator": "eq", "value": "{{params.id}}" }]
}
json
{
  "collection": "posts",
  "operation": "update",
  "docId": "{{params.id}}",
  "data": { "title": "{{body.title}}" }
}
json
{
  "table": "posts",
  "operation": "update",
  "data": { "title": "{{body.title}}" },
  "filters": [{ "column": "id", "operator": "eq", "value": "{{params.id}}" }]
}

Upsert / Set

json
{
  "table": "settings",
  "operation": "upsert",
  "data": {
    "user_id": "{{auth.sub}}",
    "theme": "{{body.theme}}"
  },
  "upsertOptions": { "onConflict": "user_id" }
}
json
{
  "collection": "settings",
  "operation": "set",
  "docId": "{{auth.sub}}",
  "data": { "theme": "{{body.theme}}" }
}
json
{
  "table": "settings",
  "operation": "upsert",
  "data": {
    "user_id": "{{auth.sub}}",
    "theme": "{{body.theme}}"
  },
  "upsertOptions": { "onConflict": "user_id" }
}

Delete

json
{
  "table": "posts",
  "operation": "delete",
  "filters": [{ "column": "id", "operator": "eq", "value": "{{params.id}}" }]
}
json
{
  "collection": "posts",
  "operation": "delete",
  "docId": "{{params.id}}"
}
json
{
  "table": "posts",
  "operation": "delete",
  "filters": [{ "column": "id", "operator": "eq", "value": "{{params.id}}" }]
}

Filters

md
| Operator | Description | Example |
|----------|-------------|---------|
| `eq` | Equal | `{ "column": "status", "operator": "eq", "value": "active" }` |
| `neq` | Not equal | `{ "column": "status", "operator": "neq", "value": "deleted" }` |
| `gt` | Greater than | `{ "column": "age", "operator": "gt", "value": 18 }` |
| `gte` | Greater or equal | `{ "column": "price", "operator": "gte", "value": 100 }` |
| `lt` | Less than | `{ "column": "stock", "operator": "lt", "value": 10 }` |
| `lte` | Less or equal | `{ "column": "rating", "operator": "lte", "value": 5 }` |
| `like` | Pattern match | `{ "column": "name", "operator": "like", "value": "%john%" }` |
| `ilike` | Case-insensitive | `{ "column": "email", "operator": "ilike", "value": "%@gmail.com" }` |
| `in` | In array | `{ "column": "status", "operator": "in", "value": ["active", "pending"] }` |
| `is` | IS NULL | `{ "column": "deleted_at", "operator": "is", "value": null }` |
| `contains` | Array contains | `{ "column": "tags", "operator": "contains", "value": ["tech"] }` |
md
| Operator | Description | Example |
|----------|-------------|---------|
| `==` | Equal | `{ "field": "status", "operator": "==", "value": "active" }` |
| `!=` | Not equal | `{ "field": "status", "operator": "!=", "value": "deleted" }` |
| `>` | Greater than | `{ "field": "age", "operator": ">", "value": 18 }` |
| `>=` | Greater or equal | `{ "field": "price", "operator": ">=", "value": 100 }` |
| `<` | Less than | `{ "field": "stock", "operator": "<", "value": 10 }` |
| `<=` | Less or equal | `{ "field": "rating", "operator": "<=", "value": 5 }` |
| `in` | In array | `{ "field": "status", "operator": "in", "value": ["active", "pending"] }` |
| `array-contains` | Array contains | `{ "field": "tags", "operator": "array-contains", "value": "tech" }` |

Multiple Filters

json
{
  "filters": [
    { "column": "status", "operator": "eq", "value": "active" },
    { "column": "created_at", "operator": "gte", "value": "2024-01-01" }
  ]
}
json
{
  "filters": [
    { "field": "status", "operator": "==", "value": "active" },
    { "field": "created_at", "operator": ">=", "value": "2024-01-01" }
  ]
}

Ordering

json
{
  "order": {
    "column": "created_at",
    "ascending": false
  }
}
json
{
  "orderBy": {
    "field": "created_at",
    "direction": "desc"
  }
}

Pagination

json
{
  "limit": 10,
  "offset": 0
}

With query parameters:

json
{
  "limit": "&#123;&#123;query.limit&#125;&#125;",
  "offset": "&#123;&#123;query.offset&#125;&#125;"
}

Relations

Supabase/SQLite support joins via select syntax:

json
{
  "table": "posts",
  "operation": "select",
  "select": "*, users(id, name, email)"
}

Nested relations:

json
{
  "select": "*, comments(*, users(name))"
}

Firebase: Use subcollections or multiple queries for relations.

Multiple Queries

json
{
  "supabaseQueries": [
    {
      "table": "orders",
      "operation": "insert",
      "data": { "user_id": "&#123;&#123;auth.sub&#125;&#125;" }
    },
    {
      "table": "inventory",
      "operation": "update",
      "data": { "stock": "&#123;&#123;body.newStock&#125;&#125;" }
    }
  ]
}
json
{
  "firebaseQueries": [
    {
      "collection": "orders",
      "operation": "add",
      "data": { "user_id": "&#123;&#123;auth.sub&#125;&#125;" }
    },
    {
      "collection": "inventory",
      "operation": "update",
      "docId": "&#123;&#123;body.itemId&#125;&#125;",
      "data": { "stock": "&#123;&#123;body.newStock&#125;&#125;" }
    }
  ]
}

Custom Databases (REST Adapter)

Add any REST-based database (MongoDB, BigQuery, etc.) via JSON config without code changes.

Configuration

Create an integration file in integrations/ folder:

json
{
  "name": "MongoDB Atlas Data API",
  "baseUrl": "&#123;&#123;secrets.MONGODB_DATA_API_URL&#125;&#125;",
  "auth": {
    "type": "api_key",
    "location": "header",
    "paramName": "api-key",
    "key": "&#123;&#123;secrets.MONGODB_API_KEY&#125;&#125;"
  },
  "defaultDatabase": "&#123;&#123;secrets.MONGODB_DATABASE&#125;&#125;",
  "operations": {
    "find": {
      "method": "POST",
      "path": "/action/find",
      "bodyTemplate": {
        "collection": "&#123;&#123;table&#125;&#125;",
        "database": "&#123;&#123;database&#125;&#125;",
        "filter": "&#123;&#123;filters&#125;&#125;",
        "limit": "&#123;&#123;limit&#125;&#125;",
        "skip": "&#123;&#123;offset&#125;&#125;",
        "sort": "&#123;&#123;sort&#125;&#125;",
        "projection": "&#123;&#123;projection&#125;&#125;"
      }
    },
    "insertOne": {
      "method": "POST",
      "path": "/action/insertOne",
      "bodyTemplate": {
        "collection": "&#123;&#123;table&#125;&#125;",
        "database": "&#123;&#123;database&#125;&#125;",
        "document": "&#123;&#123;data&#125;&#125;"
      }
    },
    "updateOne": {
      "method": "POST",
      "path": "/action/updateOne",
      "bodyTemplate": {
        "collection": "&#123;&#123;table&#125;&#125;",
        "database": "&#123;&#123;database&#125;&#125;",
        "filter": "&#123;&#123;filters&#125;&#125;",
        "update": { "$set": "&#123;&#123;data&#125;&#125;" }
      }
    },
    "deleteOne": {
      "method": "POST",
      "path": "/action/deleteOne",
      "bodyTemplate": {
        "collection": "&#123;&#123;table&#125;&#125;",
        "database": "&#123;&#123;database&#125;&#125;",
        "filter": "&#123;&#123;filters&#125;&#125;"
      }
    }
  },
  "operatorMap": {
    "eq": "$eq",
    "neq": "$ne",
    "gt": "$gt",
    "gte": "$gte",
    "lt": "$lt",
    "lte": "$lte",
    "in": "$in",
    "like": "$regex"
  },
  "responseMap": {
    "find": "documents",
    "findOne": "document",
    "insertOne": "insertedId"
  }
}

Auth Types

TypeFields
bearertoken or key
api_keykey, location (header/query), paramName
basicusername, password
customheaders object

Operator Mapping

Map generic operators to database-specific syntax:

json
{
  "operatorMap": {
    "eq": "$eq",
    "neq": "$ne",
    "gt": "$gt",
    "gte": "$gte",
    "lt": "$lt",
    "lte": "$lte",
    "in": "$in",
    "like": "$regex"
  }
}

OR Queries

Execute multiple queries and merge results (deduplicated by ID):

json
{
  "table": "users",
  "or": [
    { "filters": [{ "column": "status", "operator": "eq", "value": "active" }] },
    { "filters": [{ "column": "role", "operator": "eq", "value": "admin" }] }
  ]
}

Chain Queries (JOINs)

Execute sequential queries with result references:

json
{
  "table": "orders",
  "chain": [
    {
      "table": "users",
      "filters": [{ "column": "email", "operator": "eq", "value": "&#123;&#123;body.email&#125;&#125;" }],
      "single": true,
      "as": "user"
    },
    {
      "table": "orders",
      "filters": [{ "column": "user_id", "operator": "eq", "value": "&#123;&#123;chain.user.id&#125;&#125;" }],
      "as": "orders"
    }
  ]
}

Use &#123;&#123;chain.alias.field&#125;&#125; to reference results from previous queries.

Workflow Usage

Use custom databases in workflows with or and chain params:

json
{
  "steps": [
    {
      "id": "get-user-orders",
      "tool": "database",
      "params": {
        "table": "orders",
        "operation": "select",
        "chain": [
          {
            "table": "users",
            "filters": [{ "column": "id", "operator": "eq", "value": "&#123;&#123;input.userId&#125;&#125;" }],
            "single": true,
            "as": "user"
          },
          {
            "table": "orders",
            "filters": [{ "column": "user_id", "operator": "eq", "value": "&#123;&#123;chain.user.id&#125;&#125;" }],
            "as": "orders"
          }
        ]
      }
    }
  ]
}

Released under the ISC License.