Kodall
Web App Deployment

SolidJS

Step-by-step guide to building, proxying, and deploying static SolidJS applications to a Kodall instance with @kodall/kodall-deploy.

This guide covers building a SolidJS Single Page Application using Vite, configuring the development proxy, integrating @kodall/kodall-client, and deploying with @kodall/kodall-deploy.

This guide demonstrates a reference integration pattern for SolidJS. Kodall natively hosts any frontend application that compiles to static web assets (HTML, JavaScript, CSS, WebAssembly).

SolidJS Live Demo

Shared Demo Mode

Deployed on Kodall instance at /solid

Want an isolated database for this demo?

Currently running in shared demo mode with the public API key. Create a private 24-hour sandbox with 1-click to test creating, editing, and deleting records without interference.

https://docs-demo.kodall.io/solid

Installation

Install @kodall/kodall-client, @kodall/kodall-deploy, and vite-plugin-solid in your SolidJS project:

BASH
npm install @kodall/kodall-client
npm install -D @kodall/kodall-deploy vite-plugin-solid

Configuration

Development Proxy

Configure proxying in vite.config.ts using the kodallProxy plugin from @kodall/kodall-deploy/vite to automatically sync with kodall-webapp.config.json:

vite.config.ts
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
import { kodallProxy } from '@kodall/kodall-deploy/vite'

export default defineConfig({
  plugins: [
    solid(),
    // Option A: Automatic Vite Plugin (Recommended)
    kodallProxy() // Automatically resolves instance from default_env
  ]

  // Option B: Manual Vite Proxy (Without Helper Plugin)
  // server: {
  //   proxy: {
  //     '/auth': { target: 'https://dev.kodall.yourcompany.com', changeOrigin: true, secure: true },
  //     '/rest': { target: 'https://dev.kodall.yourcompany.com', changeOrigin: true, secure: true },
  //     '/storage': { target: 'https://dev.kodall.yourcompany.com', changeOrigin: true, secure: true }
  //   }
  // }
})
kodallProxy() automatically resolves target URLs, custom proxy_paths, and proxy routes from default_env in your kodall-webapp.config.json. To target a specific environment, pass { env: 'staging' } or explore the Local Dev Proxy Suite.

Client Setup

Create a shared instance of KodallClient:

src/lib/kodall.ts
import { KodallClient } from '@kodall/kodall-client'

export const client = new KodallClient()
Because the app runs on the same origin in production and is proxied during local development, new KodallClient() requires no hardcoded baseUrl or credentials.

Deployment Configuration (kodall-webapp.config.json)

SolidJS compiles static files to ./dist:

kodall-webapp.config.json
{
  "web_app_name": "SolidJS Dashboard",
  "web_app_path": "/",
  "dist_path": "./dist",
  "default_env": "dev",
  "environments": {
    "dev": {
      "type": "dev",
      "instance": "https://dev.kodall.yourcompany.com",
      "api_key": "dev-api-key-here"
    },
    "prod": {
      "type": "prod",
      "instance": "https://kodall.yourcompany.com",
      "api_key": "prod-api-key-here"
    }
  }
}
Instance Configuration: Do not use https://docs-demo.kodall.io. Enter the base URL of your own Kodall instance deployed from the Kodall Cloud Console.

Usage

Authentication & Session Restore

Support both API Key (stateless bearer) and Username/Password (cookie session) using SolidJS Signals with anti-flicker loading:

src/App.tsx
import { createSignal, onMount, Show } from 'solid-js'
import { KodallClient, isProblem } from '@kodall/kodall-client'

export function App() {
  const [isInitializing, setIsInitializing] = createSignal(true)
  const [isAuthenticated, setIsAuthenticated] = createSignal(false)
  const [apiKey, setApiKey] = createSignal('')
  const [username, setUsername] = createSignal('')
  const [password, setPassword] = createSignal('')

  const getClient = (customKey?: string): KodallClient => {
    return new KodallClient({ apiKey: customKey || apiKey() || undefined })
  }

  // Connect with API Key
  const handleConnectApiKey = async (keyInput?: string) => {
    const key = keyInput || apiKey()
    if (!key.trim()) return
    const client = getClient(key)
    const result = await client.fetch('FETCH todo (key) LIMIT 1')
    if (!isProblem(result)) {
      setIsAuthenticated(true)
      localStorage.setItem('kodall_is_logged_in', 'true')
      localStorage.setItem('kodall_auth_mode', 'apikey')
      localStorage.setItem('kodall_api_key', key)
    }
  }

  // Login with Username & Password
  const handleLoginBasic = async (uInput?: string, pInput?: string) => {
    const u = uInput || username()
    const p = pInput || password()
    if (!u || !p) return
    const client = getClient('')
    const result = await client.auth({ user: u, password: p }, { roles: true })
    if (!isProblem(result)) {
      setIsAuthenticated(true)
      localStorage.setItem('kodall_is_logged_in', 'true')
      localStorage.setItem('kodall_auth_mode', 'basic')
    }
  }

  // Restore session on mount (anti-flicker splash)
  onMount(async () => {
    try {
      const params = new URLSearchParams(window.location.search)
      const urlApiKey = params.get('apiKey')
      if (urlApiKey) setApiKey(urlApiKey)

      if (localStorage.getItem('kodall_is_logged_in') === 'true') {
        const mode = localStorage.getItem('kodall_auth_mode')
        if (mode === 'basic') await handleLoginBasic()
        else await handleConnectApiKey()
      }
    } finally {
      setIsInitializing(false)
    }
  })

  return (
    <main>
      <Show when={isInitializing()}>
        <div class="loading-splash"><p>Connecting to Kodall...</p></div>
      </Show>

      <Show when={!isInitializing() && !isAuthenticated()}>
        <!-- Auth Screen -->
        <button onClick={() => handleConnectApiKey()}>Connect with API Key</button>
        <button onClick={() => handleLoginBasic()}>Sign In with Password</button>
      </Show>

      <Show when={!isInitializing() && isAuthenticated()}>
        <!-- Dashboard -->
      </Show>
    </main>
  )
}

Parent-Child Entity Operations

Fetch hierarchical parent-child entities and create nested graphs atomically:

TYPESCRIPT
// 1. Fetch joined rows
const result = await client.fetch<any>(`
  FETCH todo (key, title, is_completed) {
    todo_item TO id_todo (key AS item_key, description, is_completed AS item_completed)
  }
  ORDER BY key DESC
  LIMIT 100
`)

// 2. Group flat rows into structured objects
function groupJoinedTodos(rows: any[]) {
  const map = new Map<number, any>()
  for (const row of rows) {
    if (!map.has(row.key)) {
      map.set(row.key, {
        key: row.key,
        title: row.title,
        is_completed: row.is_completed,
        children: { todo_todo_item: [] }
      })
    }
    if (row.item_key != null) {
      map.get(row.key).children.todo_todo_item.push({
        key: row.item_key,
        description: row.description,
        is_completed: row.item_completed
      })
    }
  }
  return Array.from(map.values())
}

// 3. Create parent entity and child items
await client.create({
  entity_name: 'todo',
  properties: {
    title: 'Deploy to Kodall',
    is_completed: 0
  },
  children: {
    todo_todo_item: [
      { entity_name: 'todo_item', properties: { description: 'Configure vite.config.ts', is_completed: 1 } },
      { entity_name: 'todo_item', properties: { description: 'Deploy application', is_completed: 0 } }
    ]
  }
})

Storage API & File Uploads

Upload files directly using Kodall's storage endpoints:

TYPESCRIPT
// Upload a file (Blob or File instance from <input type="file">)
const response = await fetch('/storage', {
  method: 'POST',
  headers: {
    'Content-Type': selectedFile.type || 'application/octet-stream',
    'X-File-Name': encodeURIComponent(selectedFile.name)
  },
  body: selectedFile
})

const storageRecord = await response.json()
console.log('Stored File ID:', storageRecord.id)

Deployment

Package Scripts

Add build and deployment scripts to package.json:

package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "deploy": "vite build && kodall-deploy -e dev",
    "deploy:prod": "vite build && kodall-deploy -e prod"
  }
}

Executing Deployment

Execute the deployment command for your target environment:

BASH
# Deploy to development environment
npm run deploy

# Or deploy to production
npm run deploy:prod

# Or run interactively
npx kodall-deploy

kodall-deploy will validate ./dist, package the compiled assets into an archive, upload it to Kodall storage, update the web_app entity, and execute a live HTTP health check ping.


Advanced Configuration

Subpath Mounting

If your app is hosted at a subpath (e.g. "web_app_path": "/portal"), configure base in vite.config.ts:

vite.config.ts
export default defineConfig({
  base: '/portal/',
  plugins: [solid()]
})

Deployment History & Rollback

Every deployment is tracked in Kodall storage. You can inspect previous versions and instantly roll back to any prior build:

BASH
# Inspect live status across instances
npx kodall-deploy --status

# View deployment history
npx kodall-deploy --history -e prod

# Roll back to the previous deployment
npx kodall-deploy --rollback -e prod

Troubleshooting

Missing index.html in ./dist

  • Cause: vite build was not run prior to deploying.
  • Solution: Ensure your deploy script calls vite build && kodall-deploy.

JSX runtime errors with SolidJS

  • Cause: Missing vite-plugin-solid in vite.config.ts.
  • Solution: Verify that plugins: [solid()] is configured in vite.config.ts.