Getting Started

This guide will walk you through creating your first Cloudillo application.

Prerequisites

  • Node.js 18+ and pnpm installed
  • Basic knowledge of TypeScript/JavaScript
  • Familiarity with React (optional, for UI apps)

Installation

For Standalone Apps

pnpm add @cloudillo/core

For React Apps

pnpm add @cloudillo/core @cloudillo/react

For Real-Time Database

pnpm add @cloudillo/rtdb

For Collaborative Editing

pnpm add @cloudillo/core yjs y-websocket

Your First App: Hello Cloudillo

Let’s create a simple app that displays the current user’s profile.

Step 1: Initialize the App

Create src/index.ts:

import { getAppBus } from '@cloudillo/core'

async function main() {
  // Get the singleton message bus
  const bus = getAppBus()

  // Initialize with your app name
  await bus.init('hello-cloudillo')

  console.log('Initialized successfully!')
  console.log('Access Token:', bus.accessToken)
  console.log('User ID:', bus.idTag)
  console.log('Tenant ID:', bus.tnId)
  console.log('Roles:', bus.roles)
}

main().catch(console.error)

Step 2: Fetch User Profile

import { getAppBus, createApiClient } from '@cloudillo/core'

async function main() {
  const bus = getAppBus()
  await bus.init('hello-cloudillo')

  // Create an API client
  const api = createApiClient({
    idTag: bus.idTag!,
    authToken: bus.accessToken
  })

  // Fetch the current user's profile
  const profile = await api.profiles.getOwn()

  console.log('Profile:', profile)
  console.log('Name:', profile.name)
  console.log('ID Tag:', profile.idTag)
  console.log('Profile Picture:', profile.profilePic)
}

main().catch(console.error)

Step 3: Create a Post

import { getAppBus, createApiClient } from '@cloudillo/core'

async function main() {
  const bus = getAppBus()
  await bus.init('hello-cloudillo')

  const api = createApiClient({
    idTag: bus.idTag!,
    authToken: bus.accessToken
  })

  // Create a new post
  const newPost = await api.actions.create({
    type: 'POST',
    content: {
      text: 'Hello from my first Cloudillo app!',
      title: 'My First Post'
    }
  })

  console.log('Post created:', newPost)
}

main().catch(console.error)

React Example

For React applications, use the provided hooks:

import React from 'react'
import { useCloudillo, useAuth, useApi } from '@cloudillo/react'

function App() {
  // useCloudillo handles initialization
  const { token } = useCloudillo('hello-cloudillo')

  if (!token) return <div>Loading...</div>

  return <Profile />
}

function Profile() {
  const [auth] = useAuth()  // Returns tuple [auth, setAuth]
  const { api } = useApi()  // Returns { api, authenticated, setIdTag }
  const [profile, setProfile] = React.useState(null)

  React.useEffect(() => {
    if (!api) return
    api.profiles.getOwn().then(setProfile)
  }, [api])

  if (!api) return <div>No API client</div>
  if (!profile) return <div>Loading...</div>

  return (
    <div>
      <h1>Welcome, {profile.name}!</h1>
      <p>ID: {auth?.idTag}</p>
      {profile.profilePic && (
        <img src={profile.profilePic} alt="Profile" />
      )}
    </div>
  )
}

export default App

Real-Time Database Example

Here’s how to use the real-time database:

import { getAppBus, getRtdbUrl } from '@cloudillo/core'
import { RtdbClient } from '@cloudillo/rtdb'

async function main() {
  const bus = getAppBus()
  await bus.init('rtdb-example')

  // Create RTDB client
  const rtdb = new RtdbClient({
    dbId: 'my-database-file-id',
    auth: { getToken: () => bus.accessToken },
    serverUrl: getRtdbUrl(bus.idTag!, 'my-database-file-id', bus.accessToken!)
  })

  // Connect to the database
  await rtdb.connect()

  // Get a collection reference
  const todos = rtdb.collection('todos')

  // Subscribe to real-time updates
  todos.onSnapshot((snapshot) => {
    console.log('Todos updated:', snapshot.docs.map(doc => doc.data()))
  })

  // Create a document using batch
  const batch = rtdb.batch()
  batch.create(todos, {
    title: 'Learn Cloudillo',
    completed: false,
    createdAt: Date.now()
  })
  await batch.commit()

  // Query documents
  const incompleteTodos = await todos.query({
    filter: { equals: { completed: false } },
    sort: [{ field: 'createdAt', ascending: false }]
  })

  console.log('Incomplete todos:', incompleteTodos)
}

main().catch(console.error)

Collaborative Editing Example

Create a collaborative text editor:

import { getAppBus, openYDoc } from '@cloudillo/core'
import * as Y from 'yjs'

async function main() {
  const bus = getAppBus()
  await bus.init('collab-editor')

  // Create a Yjs document
  const yDoc = new Y.Doc()

  // Open collaborative document (format: ownerTag:documentId)
  const { provider } = await openYDoc(yDoc, 'owner.cloudillo.net:my-doc-id')

  // Get shared text
  const yText = yDoc.getText('content')

  // Listen for changes
  yText.observe(() => {
    console.log('Text changed:', yText.toString())
  })

  // Insert text
  yText.insert(0, 'Hello, collaborative world!')

  // See awareness (other users' cursors/selections)
  provider.awareness.on('change', () => {
    const states = provider.awareness.getStates()
    console.log('Connected users:', states.size)
  })
}

main().catch(console.error)

Microfrontend Integration

If you’re building an app to run inside the Cloudillo shell:

import { getAppBus, createApiClient } from '@cloudillo/core'

async function main() {
  // Get the singleton message bus
  const bus = getAppBus()

  // init() automatically handles the shell protocol
  await bus.init('my-microfrontend')

  // Create an API client
  const api = createApiClient({
    idTag: bus.idTag!,
    authToken: bus.accessToken
  })

  // The shell provides via bus properties:
  // - bus.idTag (user's identity)
  // - bus.tnId (tenant ID)
  // - bus.roles (user roles)
  // - bus.darkMode (theme preference)
  // - bus.access ('read' or 'write')

  // Your app logic here...
}

main().catch(console.error)

Error Handling

All API calls can throw errors. Handle them appropriately:

import { getAppBus, createApiClient } from '@cloudillo/core'

const bus = getAppBus()
await bus.init('my-app')

try {
  const api = createApiClient({
    idTag: bus.idTag!,
    authToken: bus.accessToken
  })
  const profile = await api.profiles.getOwn()
} catch (error) {
  if (error instanceof Error) {
    console.error('Error:', error.message)
  }
}

Next Steps

Now that you’ve created your first Cloudillo app, explore more features:

Common Patterns

Handling Dark Mode

import { getAppBus } from '@cloudillo/core'

const bus = getAppBus()
await bus.init('my-app')

// Check dark mode preference
if (bus.darkMode) {
  document.body.classList.add('dark-theme')
}

Using Query Parameters

import { getAppBus, createApiClient } from '@cloudillo/core'

const bus = getAppBus()
await bus.init('my-app')

const api = createApiClient({
  idTag: bus.idTag!,
  authToken: bus.accessToken
})

// List actions with filters
const posts = await api.actions.list({
  type: 'POST',
  status: 'A', // Active
  limit: 20
})

Uploading Files

import { getAppBus, createApiClient } from '@cloudillo/core'

const bus = getAppBus()
await bus.init('my-app')

const api = createApiClient({
  idTag: bus.idTag!,
  authToken: bus.accessToken
})

// Upload a file using the uploadBlob helper
const result = await api.files.uploadBlob(
  'gallery',      // preset
  'image.png',    // fileName
  imageBlob,      // file data
  'image/png'     // contentType
)

console.log('Uploaded file:', result.fileId)

Build and Deploy

Development

Most apps run as microfrontends inside the Cloudillo shell. Use your preferred build tool (Rollup, Webpack, Vite):

# Using Rollup (like the example apps)
pnpm build

# Using Vite
vite build

Production

Deploy your built app to any static hosting:

# The built output goes to the shell's apps directory
cp -r dist /path/to/cloudillo/shell/public/apps/my-app

Troubleshooting

“Failed to initialize”

Make sure you’re either:

  1. Running inside the Cloudillo shell (as a microfrontend), or
  2. Providing authentication manually for standalone apps

“CORS errors”

Ensure your Cloudillo server is configured to allow requests from your app’s origin.

“WebSocket connection failed”

Check that:

  1. The WebSocket URL is correct (wss:// for production)
  2. The server is running and accessible
  3. Your authentication token is valid

Example Apps

Check out the example apps in the Cloudillo repository:

  • Quillo - Rich text editor with Quill
  • Prello - Presentation tool
  • Sheello - Spreadsheet application
  • Formillo - Form builder
  • Todollo - Task management

All use the same patterns described in this guide.