Files
OpenNoodl/dev-docs/future-projects/NATIVE-BAAS-INTEGRATIONS.md

15 KiB

FUTURE: Native BaaS Integration Nodes

Document Type: Future Project Scoping
Status: Planning
Prerequisites: TASK-002 (Robust HTTP Node)
Estimated Effort: 2-4 weeks per BaaS
Priority: High (post-HTTP node completion)

Executive Summary

This document outlines the strategy for adding native Backend-as-a-Service (BaaS) integrations to OpenNoodl. The goal is to provide the same seamless "pick a table, see the fields" experience that Parse Server nodes currently offer, but for popular BaaS platforms that the community is asking for.

The key insight: Noodl's Parse nodes demonstrate that schema-aware nodes dramatically improve the low-code experience. When you select a table and immediately see all available fields as input/output ports, you eliminate the manual configuration that makes the current REST node painful.

The Problem

Community feedback: "How do I hook up my backend?" is the #1 question from new Noodl users.

Current options:

  1. Parse Server nodes - Great UX, but Parse isn't everyone's choice
  2. REST node - Requires JavaScript scripting, intimidating for nocoders
  3. Function node - Powerful but even more code-heavy
  4. AI-generated Function nodes - Works but feels like a workaround

Users coming from other low-code platforms (n8n, Flutterflow, Retool) expect to see their backend in a dropdown and start building immediately.

Strategic Approach

Two-Track Strategy

Track 1: Robust HTTP Node (TASK-002)

  • Foundation for any API integration
  • Declarative, no-code configuration
  • cURL import for quick setup
  • The "escape hatch" that works with anything

Track 2: Native BaaS Modules (This Document)

  • Schema-aware nodes for specific platforms
  • Dropdown table selection → automatic field ports
  • Visual query builders
  • Authentication handled automatically

These tracks are complementary:

  • HTTP Node = "You can connect to anything"
  • BaaS Nodes = "Connecting to X is effortless"

Module Architecture

Each BaaS integration ships as an installable Noodl Module (like MQTT or Material Icons):

noodl_modules/
├── supabase/
│   ├── manifest.json
│   ├── index.js
│   └── nodes/
│       ├── SupabaseConfig.js      # Connection configuration
│       ├── SupabaseQuery.js       # Read records
│       ├── SupabaseInsert.js      # Create records
│       ├── SupabaseUpdate.js      # Update records
│       ├── SupabaseDelete.js      # Delete records
│       ├── SupabaseRealtime.js    # Live subscriptions
│       └── SupabaseAuth.js        # Authentication
├── pocketbase/
│   └── ...
└── directus/
    └── ...

Benefits of module approach:

  • Core Noodl stays lean
  • Users opt-in to what they need
  • Independent update cycles
  • Community can contribute modules
  • Easier to maintain

Layered Implementation

┌─────────────────────────────────────────┐
│     BaaS Node (UX Layer)                │  ← Table dropdown, field ports, visual filters
├─────────────────────────────────────────┤
│     BaaS Adapter (Logic Layer)          │  ← Schema introspection, query translation
├─────────────────────────────────────────┤
│     HTTP Primitive (Transport Layer)    │  ← Actual HTTP requests (from TASK-002)
└─────────────────────────────────────────┘

This means:

  • One HTTP implementation to maintain
  • BaaS modules are mostly "schema + translation"
  • Debugging is easier (can inspect raw HTTP)
  • HTTP node improvements benefit all BaaS modules

BaaS Platform Analysis

Priority 1: Supabase

Why first:

  • Most requested by community
  • Excellent schema introspection via PostgREST
  • PostgreSQL is familiar and powerful
  • Strong ecosystem and documentation
  • Free tier makes it accessible

Schema Introspection:

# Supabase exposes OpenAPI spec at root
GET https://your-project.supabase.co/rest/v1/
# Returns full schema with tables, columns, types, relationships

Node Set:

Node Purpose Key Features
Supabase Config Store connection URL, anon key, service key
Query Records SELECT Table dropdown, column selection, filters, sorting, pagination
Insert Record INSERT Table dropdown, field inputs from schema
Update Record UPDATE Table dropdown, field inputs, row identifier
Delete Record DELETE Table dropdown, row identifier
Realtime Subscribe Live data Table + filter, outputs on change
Auth (Sign Up) Create user Email, password, metadata
Auth (Sign In) Authenticate Email/password, magic link, OAuth
Auth (User) Current user Session data, JWT
Storage Upload File upload Bucket selection, file input
Storage Download File URL Bucket, path → signed URL
RPC Call Stored procedures Function dropdown, parameter inputs

Technical Details:

  • Auth: Uses Supabase Auth (GoTrue)
  • Realtime: WebSocket connection to Supabase Realtime
  • Storage: S3-compatible API
  • Query: PostgREST syntax (filters, operators, pagination)

Estimated Effort: 2-3 weeks


Priority 2: Pocketbase

Why second:

  • Growing rapidly in low-code community
  • Simple, single-binary deployment
  • Good schema API
  • Simpler than Supabase (faster to implement)
  • Self-hosting friendly

Schema Introspection:

# Pocketbase admin API returns collection schema
GET /api/collections
# Returns: name, type, schema (fields with types), options

Node Set:

Node Purpose Key Features
Pocketbase Config Store connection URL, admin credentials
List Records Query Collection dropdown, filter, sort, expand relations
View Record Get one Collection, record ID
Create Record Insert Collection dropdown, field inputs
Update Record Modify Collection, record ID, field inputs
Delete Record Remove Collection, record ID
Realtime Subscribe Live data Collection + filter
Auth User management Email/password, OAuth providers
File URL Get file URL Record, field name

Technical Details:

  • Simpler auth model than Supabase
  • Built-in file handling per record
  • Realtime via SSE (Server-Sent Events)
  • Filter syntax is custom (not PostgREST)

Estimated Effort: 1.5-2 weeks


Priority 3: Directus

Why third:

  • Enterprise-focused, more complex
  • Headless CMS capabilities
  • Strong schema introspection
  • GraphQL support
  • Longer implementation due to complexity

Schema Introspection:

# Directus has comprehensive schema endpoint
GET /fields
GET /collections
GET /relations
# Returns detailed field metadata including UI hints

Node Set:

Node Purpose Key Features
Directus Config Store connection URL, access token
Get Items Query Collection dropdown, fields, filter, sort
Get Item Single Collection, ID
Create Item Insert Collection, field inputs
Update Item Modify Collection, ID, field inputs
Delete Item Remove Collection, ID
Assets File handling Upload, get URL
Auth Authentication Login, refresh, current user

Technical Details:

  • REST and GraphQL APIs available
  • More complex permission model
  • Richer field types (including custom)
  • Flows/automation integration possible

Estimated Effort: 2-3 weeks


Technical Deep Dive

Schema Introspection Pattern

All BaaS modules follow this pattern:

// 1. On config change, fetch schema
async function fetchSchema(config) {
  const response = await fetch(`${config.url}/schema-endpoint`, {
    headers: { 'Authorization': `Bearer ${config.apiKey}` }
  });
  return response.json();
}

// 2. Store schema in editor context
context.editorConnection.sendMetadata({
  type: 'baas-schema',
  provider: 'supabase',
  tables: schema.definitions,
  // Cache key for invalidation
  hash: computeHash(schema)
});

// 3. Nodes consume schema for dynamic ports
function updatePorts(node, schema) {
  const table = node.parameters.table;
  const tableSchema = schema.tables[table];
  
  if (!tableSchema) return;
  
  const ports = [];
  
  // Create input ports for each column
  Object.entries(tableSchema.columns).forEach(([name, column]) => {
    ports.push({
      name: `field-${name}`,
      displayName: name,
      type: mapColumnType(column.type),
      plug: 'input',
      group: 'Fields'
    });
  });
  
  // Create output ports
  ports.push({
    name: 'result',
    displayName: 'Result',
    type: 'array',
    plug: 'output',
    group: 'Results'
  });
  
  context.editorConnection.sendDynamicPorts(node.id, ports);
}

Query Translation

Each BaaS has different filter syntax. The adapter translates from Noodl's visual filter format:

// Noodl visual filter format (from QueryEditor)
const noodlFilter = {
  combinator: 'and',
  rules: [
    { property: 'status', operator: 'equalTo', value: 'active' },
    { property: 'created_at', operator: 'greaterThan', input: 'startDate' }
  ]
};

// Supabase (PostgREST) translation
function toSupabaseFilter(filter) {
  return filter.rules.map(rule => {
    switch(rule.operator) {
      case 'equalTo': return `${rule.property}=eq.${rule.value}`;
      case 'greaterThan': return `${rule.property}=gt.${rule.value}`;
      // ... more operators
    }
  }).join('&');
}

// Pocketbase translation
function toPocketbaseFilter(filter) {
  return filter.rules.map(rule => {
    switch(rule.operator) {
      case 'equalTo': return `${rule.property}="${rule.value}"`;
      case 'greaterThan': return `${rule.property}>"${rule.value}"`;
      // ... more operators
    }
  }).join(' && ');
}

Authentication Flow

Each module handles auth internally:

// Supabase example
const SupabaseConfig = {
  name: 'Supabase Config',
  category: 'Supabase',
  
  inputs: {
    projectUrl: { type: 'string', displayName: 'Project URL' },
    anonKey: { type: 'string', displayName: 'Anon Key' },
    // Service key for admin operations (optional)
    serviceKey: { type: 'string', displayName: 'Service Key' }
  },
  
  // Store config globally for other nodes to access
  methods: {
    setConfig: function() {
      this.context.globalStorage.set('supabase-config', {
        url: this._internal.projectUrl,
        anonKey: this._internal.anonKey,
        serviceKey: this._internal.serviceKey
      });
      this.sendSignalOnOutput('configured');
    }
  }
};

// Other Supabase nodes retrieve config
const SupabaseQuery = {
  methods: {
    doQuery: async function() {
      const config = this.context.globalStorage.get('supabase-config');
      if (!config) throw new Error('Supabase not configured');
      
      const response = await fetch(
        `${config.url}/rest/v1/${this._internal.table}`,
        {
          headers: {
            'apikey': config.anonKey,
            'Authorization': `Bearer ${config.anonKey}`
          }
        }
      );
      // ... handle response
    }
  }
};

Visual Filter Builder Integration

Reuse existing QueryEditor components with BaaS-specific schema:

// In editor, when Supabase node is selected
const schema = getSupabaseSchema(node.parameters.table);

// Pass to QueryEditor
<QueryFilterEditor
  schema={schema}
  value={node.parameters.visualFilter}
  onChange={(filter) => node.setParameter('visualFilter', filter)}
/>

The existing QueryEditor components from Parse integration can be reused:

  • QueryRuleEditPopup
  • QuerySortingEditor
  • RuleDropdown, RuleInput

Implementation Phases

Phase 1: Foundation (TASK-002)

  • Complete Robust HTTP Node
  • Establish patterns for dynamic ports
  • Create reusable editor components

Phase 2: Supabase Module

Week 1:

  • Schema introspection implementation
  • Config node
  • Query node with table dropdown

Week 2:

  • Insert, Update, Delete nodes
  • Visual filter builder integration
  • Field-to-port mapping

Week 3:

  • Realtime subscriptions
  • Authentication nodes
  • Storage nodes
  • Documentation and examples

Phase 3: Pocketbase Module

Week 1-2:

  • Schema introspection
  • Core CRUD nodes
  • Realtime via SSE
  • Authentication
  • Documentation

Phase 4: Directus Module

Week 2-3:

  • Schema introspection (more complex)
  • Core CRUD nodes
  • Asset management
  • Documentation

Phase 5: Community & Iteration

  • Publish module development guide
  • Community feedback integration
  • Additional BaaS based on demand (Firebase, Appwrite, etc.)

Success Metrics

Metric Target
Time to first query < 5 minutes (with Supabase account)
Lines of code to query 0 (visual only)
Schema sync delay < 2 seconds
Community satisfaction Positive feedback in Discord
Module adoption 50% of new projects using a BaaS module

Risks & Mitigations

Risk Impact Mitigation
BaaS API changes High Version pin, monitor changelogs
Schema introspection rate limits Medium Cache aggressively, manual refresh
Complex filter translation Medium Start simple, iterate based on feedback
Module maintenance burden Medium Community contributions, shared patterns
Authentication complexity High Follow each BaaS's recommended patterns

Open Questions

  1. Should modules auto-detect connection issues?

    • e.g., "Can't reach Supabase - check your URL"
  2. How to handle schema changes?

    • Auto-refresh? Manual button? Both?
  3. Should we support multiple instances per BaaS?

    • e.g., "Supabase Production" vs "Supabase Staging"
  4. How to handle migrations?

    • If user changes BaaS provider, any tooling to help?
  5. GraphQL support for Directus/Supabase?

    • PostgREST is simpler, but GraphQL is more flexible

References

Supabase

Pocketbase

Directus

Noodl Internals


Appendix: Community Quotes

"I'm used to Flutterflow where I just pick Supabase and I'm done. In Noodl I have to figure out REST nodes and it's confusing." - Discord user

"The Parse nodes are amazing, why can't we have that for other backends?" - Forum post

"I tried using the Function node for Supabase but I'm not a developer, I don't know JavaScript." - New user feedback

"If Noodl had native Supabase support I'd switch from Flutterflow tomorrow." - Potential user