Kodall
Web App Deployment

Angular (17+)

Step-by-step guide to building, proxying, and deploying modern Angular (17+) applications to a Kodall instance with @kodall/kodall-deploy.

This guide covers building modern Angular (v17+) standalone applications, configuring the Angular development proxy, injecting @kodall/kodall-client, and deploying with @kodall/kodall-deploy.

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

Angular (17+) Live Demo

Shared Demo Mode

Deployed on Kodall instance at /angular

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/angular

Installation

Install @kodall/kodall-client and @kodall/kodall-deploy in your Angular project:

BASH
npm install @kodall/kodall-client
npm install -D @kodall/kodall-deploy

Configuration

Development Proxy

Create proxy.conf.js using @kodall/kodall-deploy's getAngularProxy() helper to automatically resolve target instances from kodall-webapp.config.json:

proxy.conf.js
const { getAngularProxy } = require('@kodall/kodall-deploy')

// Automatically resolves instance from default_env
module.exports = getAngularProxy()

Reference the proxy in angular.json:

angular.json
"serve": {
  "builder": "@angular-devkit/build-angular:dev-server",
  "options": {
    "proxyConfig": "proxy.conf.js"
  }
}

Option B: Manual proxy.conf.json (Without Helper Package)

Create a static proxy.conf.json configuration:

proxy.conf.json
{
  "/auth": { "target": "https://dev.kodall.yourcompany.com", "secure": true, "changeOrigin": true },
  "/rest": { "target": "https://dev.kodall.yourcompany.com", "secure": true, "changeOrigin": true },
  "/storage": { "target": "https://dev.kodall.yourcompany.com", "secure": true, "changeOrigin": true }
}

Reference proxy.conf.json in angular.json:

angular.json
"serve": {
  "builder": "@angular-devkit/build-angular:dev-server",
  "options": {
    "proxyConfig": "proxy.conf.json"
  }
}
getAngularProxy() automatically configures proxying for /auth, /rest, /storage, and custom proxy_paths from default_env in kodall-webapp.config.json. To target a specific environment, pass { env: 'staging' } or explore the Local Dev Proxy Suite.

Client Setup

Create an injectable Angular service to provide a shared instance of KodallClient:

src/app/services/kodall.service.ts
import { Injectable } from '@angular/core';
import { KodallClient } from '@kodall/kodall-client';

@Injectable({
  providedIn: 'root'
})
export class KodallService {
  public 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)

Angular 17+ compiles standalone application bundles to ./dist/<project-name>/browser:

kodall-webapp.config.json
{
  "web_app_name": "Angular Dashboard",
  "web_app_path": "/",
  "dist_path": "./dist/my-app/browser",
  "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 token) and Username/Password (cookie session) using Angular Signals:

src/app/app.ts
import { Component, OnInit, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { KodallClient, isProblem } from '@kodall/kodall-client';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './app.html',
})
export class App implements OnInit {
  public isInitializing = signal<boolean>(true);
  public isAuthenticated = signal<boolean>(false);
  public apiKey = signal<string>('');
  public username = signal<string>('');
  public password = signal<string>('');

  public getClient(customKey?: string): KodallClient {
    return new KodallClient({ apiKey: customKey || this.apiKey() || undefined });
  }

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

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

  // Restore session on mount (anti-flicker loading splash)
  ngOnInit() {
    const init = async () => {
      try {
        const params = new URLSearchParams(window.location.search);
        const urlApiKey = params.get('apiKey');
        if (urlApiKey) this.apiKey.set(urlApiKey);

        if (localStorage.getItem('kodall_is_logged_in') === 'true') {
          const mode = localStorage.getItem('kodall_auth_mode');
          if (mode === 'basic') await this.handleLoginBasic();
          else await this.handleConnectApiKey();
        }
      } finally {
        this.isInitializing.set(false);
      }
    };
    init();
  }
}
src/app/app.html
<!-- Anti-flicker loading splash -->
@if (isInitializing()) {
  <div class="loading-splash">
    <p>Connecting to Kodall...</p>
  </div>
} @else if (!isAuthenticated()) {
  <!-- Auth Screen -->
  <section class="auth-card">
    <button (click)="handleConnectApiKey()">Connect with API Key</button>
    <button (click)="handleLoginBasic()">Sign In with Password</button>
  </section>
} @else {
  <!-- Main Dashboard -->
  <div class="dashboard">...</div>
}

Parent-Child Entity Operations

Fetch hierarchical parent-child relationships 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 tree
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 proxy.conf.json', is_completed: 1 } },
      { entity_name: 'todo_item', properties: { description: 'Deploy application', is_completed: 0 } }
    ]
  }
});

Storage API & File Uploads

Upload and download 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": {
    "start": "ng serve",
    "build": "ng build",
    "deploy": "ng build && kodall-deploy -e dev",
    "deploy:prod": "ng 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 your build directory, package the application bundle, upload the archive to Kodall storage, update the web_app entity, and execute a live HTTP health check ping.


Advanced Configuration

Subpath Mounting

If your app is deployed to a subpath (e.g. "web_app_path": "/portal"), build with --base-href:

package.json
{
  "scripts": {
    "deploy": "ng build --base-href /portal/ && kodall-deploy -e dev",
    "deploy:prod": "ng build --base-href /portal/ && kodall-deploy -e prod"
  }
}

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/<app>/browser

  • Cause: Angular 17+ uses the new Application builder outputting to dist/<app>/browser.
  • Solution: Ensure dist_path in kodall-webapp.config.json points to ./dist/<project-name>/browser.

Proxy configuration not loading in ng serve

  • Cause: proxyConfig is missing from angular.json.
  • Solution: Check that "proxyConfig": "proxy.conf.json" is configured under architect.serve.options.