Getting Started

Getting Started with Cloudillo Development

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/base @cloudillo/types

For React Apps

pnpm add @cloudillo/base @cloudillo/types @cloudillo/react

For Real-Time Database

pnpm add @cloudillo/rtdb

For Collaborative Editing

pnpm add @cloudillo/base 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 * as cloudillo from '@cloudillo/base'

async function main() {
  // Initialize with your app name
  const token = await cloudillo.init('hello-cloudillo')

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

main().catch(console.error)

Step 2: Fetch User Profile

import * as cloudillo from '@cloudillo/base'

async function main() {
  await cloudillo.init('hello-cloudillo')

  // Create an API client
  const api = cloudillo.createApiClient()

  // Fetch the current user's profile
  const profile = await api.me.get()

  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 * as cloudillo from '@cloudillo/base'

async function main() {
  await cloudillo.init('hello-cloudillo')
  const api = cloudillo.createApiClient()

  // Create a new post
  const newPost = await api.action.post({
    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 { CloudilloProvider, useAuth, useApi } from '@cloudillo/react'

function App() {
  return (
    <CloudilloProvider appName="hello-cloudillo">
      <Profile />
    </CloudilloProvider>
  )
}

function Profile() {
  const auth = useAuth()
  const api = useApi()
  const [profile, setProfile] = React.useState(null)

  React.useEffect(() => {
    api.me.get().then(setProfile)
  }, [api])

  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 * as cloudillo from '@cloudillo/base'
import { RtdbClient } from '@cloudillo/rtdb'

async function main() {
  const token = await cloudillo.init('rtdb-example')

  // Create RTDB client
  const rtdb = new RtdbClient({
    fileId: 'my-database-file-id',
    token,
    url: 'wss://your-server.com/ws/rtdb'
  })

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

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

  // Create a document
  await todos.create({
    title: 'Learn Cloudillo',
    completed: false,
    createdAt: Date.now()
  })

  // Query documents
  const incompleteTodos = await todos
    .where('completed', '==', false)
    .orderBy('createdAt', 'desc')
    .get()

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

main().catch(console.error)

Collaborative Editing Example

Create a collaborative text editor:

import * as cloudillo from '@cloudillo/base'
import * as Y from 'yjs'

async function main() {
  await cloudillo.init('collab-editor')

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

  // Open collaborative document
  const { provider } = await cloudillo.openYDoc(yDoc, '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 * as cloudillo from '@cloudillo/base'

// The init() function automatically handles the shell protocol
async function main() {
  const token = await cloudillo.init('my-microfrontend')

  // Now you can use all Cloudillo features
  const api = cloudillo.createApiClient()

  // The shell provides:
  // - cloudillo.idTag (user's identity)
  // - cloudillo.tnId (tenant ID)
  // - cloudillo.roles (user roles)
  // - cloudillo.darkMode (theme preference)

  // Your app logic here...
}

main().catch(console.error)

Error Handling

All API calls can throw errors. Handle them appropriately:

import { FetchError } from '@cloudillo/base'

try {
  const api = cloudillo.createApiClient()
  const profile = await api.me.get()
} catch (error) {
  if (error instanceof FetchError) {
    console.error('API Error:', error.message)
    console.error('Status:', error.status)
    console.error('Code:', error.code) // e.g., 'E-AUTH-UNAUTH'
  } else {
    console.error('Unexpected error:', error)
  }
}

Next Steps

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

Common Patterns

Handling Dark Mode

import * as cloudillo from '@cloudillo/base'

await cloudillo.init('my-app')

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

Using Query Parameters

const api = cloudillo.createApiClient()

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

Uploading Files

const api = cloudillo.createApiClient()

// Create file metadata
const file = await api.file.post({
  fileTp: 'BLOB',
  contentType: 'image/png'
})

// Upload binary data
const formData = new FormData()
formData.append('file', imageBlob)
formData.append('fileId', file.fileId)

await api.file.upload.post(formData)

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.