Quick Start

Quick Start Guide

Get up and running with the Cloudillo API in minutes. This guide shows you how to accomplish common tasks.

Prerequisites

  • Node.js 16+ installed
  • A Cloudillo server instance (local or hosted)
  • Basic JavaScript/TypeScript knowledge

Installation

npm install @cloudillo/base

1. Initialize and Authenticate

Register a New Account

import * as cloudillo from '@cloudillo/base'

// Initialize the library
await cloudillo.init('my-app', {
  serverUrl: 'https://your-server.com'
})

// Register new user
const registerResponse = await fetch('/api/auth/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    idTag: 'alice@example.com',
    password: 'secure-password-123',
    name: 'Alice Johnson'
  })
})

const { data } = await registerResponse.json()
console.log('Registered! Token:', data.token)

// Store token for future requests
localStorage.setItem('cloudillo_token', data.token)

Login to Existing Account

const loginResponse = await fetch('/api/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    idTag: 'alice@example.com',
    password: 'secure-password-123'
  })
})

const { data } = await loginResponse.json()
localStorage.setItem('cloudillo_token', data.token)

// Create authenticated API client
const api = cloudillo.createApiClient({
  token: data.token
})

2. Get User Profile

// Get your own profile
const myProfile = await api.me.get()

console.log('My profile:', myProfile.data)
// {
//   tnId: 12345,
//   idTag: 'alice@example.com',
//   name: 'Alice Johnson',
//   profilePic: '/api/file/b1~abc123'
// }

3. Create a Post

// Create a simple text post
const post = await api.action.post({
  type: 'POST',
  content: {
    text: 'Hello, Cloudillo! This is my first post.',
    title: 'My First Post'
  }
})

console.log('Post created:', post.data.actionId)

Create a Post with Images

// First, upload an image
const fileInput = document.querySelector('input[type="file"]')
const imageFile = fileInput.files[0]

const uploadResponse = await fetch(`/api/file/default/${encodeURIComponent(imageFile.name)}?tags=post,photo`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': imageFile.type
  },
  body: imageFile
})

const { data: fileData } = await uploadResponse.json()
const fileId = fileData.fileId

// Create post with attachment
const postWithImage = await api.action.post({
  type: 'POST',
  content: {
    text: 'Check out this photo!',
    title: 'Beach Sunset'
  },
  attachments: [fileId]
})

console.log('Post with image:', postWithImage.data.actionId)

4. Get Recent Posts

// Get recent posts from everyone
const posts = await api.action.get({
  type: 'POST',
  status: 'A',
  _limit: 20,
  _expand: 'issuer',
  _sort: 'createdAt',
  _order: 'desc'
})

posts.data.forEach(post => {
  console.log(`${post.issuer.name}: ${post.content.text}`)
})

// Example output:
// Alice Johnson: Hello, Cloudillo!
// Bob Smith: Just joined!
// Carol Davis: Having a great day!

5. Comment on a Post

// Add a comment to a post
const comment = await api.action.post({
  type: 'CMNT',
  parentId: 'act_post123',
  content: {
    text: 'Great post! Thanks for sharing.'
  }
})

console.log('Comment added:', comment.data.actionId)

// Get all comments on a post
const comments = await api.action.get({
  type: 'CMNT',
  parentId: 'act_post123',
  _expand: 'issuer',
  _sort: 'createdAt',
  _order: 'asc'
})

console.log(`${comments.data.length} comments`)

6. React to Content

// Add a reaction (like, love, etc.)
const reaction = await api.action.id('act_post123').reaction.post({
  type: 'LOVE'
})

console.log('Reaction added:', reaction.data.actionId)

// Get actions with statistics
const post = await api.action.id('act_post123').get()

console.log('Statistics:', post.data.stat)
// {
//   reactions: 15,
//   comments: 8,
//   ownReaction: 'LOVE'
// }

7. Follow a User

// Follow another user
const follow = await api.action.post({
  type: 'FLLW',
  subject: 'bob@example.com'
})

console.log('Now following bob@example.com')

// Get list of people you follow
const following = await api.action.get({
  type: 'FLLW',
  issuerTag: cloudillo.idTag,
  status: 'A'
})

console.log(`Following ${following.data.length} users`)

8. Upload and Manage Files

Upload Profile Picture

const imageInput = document.querySelector('input[type="file"]')
const profileImage = imageInput.files[0]

const response = await fetch('/api/me/image', {
  method: 'PUT',
  headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': profileImage.type
  },
  body: profileImage
})

const { data } = await response.json()
console.log('Profile picture updated:', data.profilePic)

Upload File with Progress

async function uploadFileWithProgress(file, onProgress) {
  const xhr = new XMLHttpRequest()

  return new Promise((resolve, reject) => {
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        const percent = (e.loaded / e.total) * 100
        onProgress(percent)
      }
    })

    xhr.addEventListener('load', () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(JSON.parse(xhr.responseText))
      } else {
        reject(new Error(xhr.statusText))
      }
    })

    xhr.addEventListener('error', () => reject(new Error('Upload failed')))

    xhr.open('POST', `/api/file/default/${encodeURIComponent(file.name)}`)
    xhr.setRequestHeader('Authorization', `Bearer ${token}`)
    xhr.setRequestHeader('Content-Type', file.type)
    xhr.send(file)
  })
}

// Usage
const result = await uploadFileWithProgress(file, (percent) => {
  console.log(`Upload progress: ${percent.toFixed(1)}%`)
  updateProgressBar(percent)
})

console.log('Upload complete:', result.data.fileId)

List Your Files

// Get all your files
const files = await api.file.get({
  fileTp: 'BLOB',
  _limit: 50,
  _sort: 'createdAt',
  _order: 'desc'
})

files.data.forEach(file => {
  console.log(`${file.fileName} - ${file.contentType}`)
})

// Filter by type
const images = await api.file.get({
  fileTp: 'BLOB',
  contentType: 'image/*'
})

console.log(`You have ${images.data.length} images`)

9. Real-time Updates (WebSocket)

// Connect to WebSocket for real-time updates
const ws = new WebSocket(`wss://your-server.com/ws/bus`)

ws.onopen = () => {
  console.log('Connected to real-time bus')

  // Subscribe to events
  ws.send(JSON.stringify({
    type: 'subscribe',
    channels: ['actions', 'messages']
  }))
}

ws.onmessage = (event) => {
  const data = JSON.parse(event.data)

  switch (data.type) {
    case 'action':
      console.log('New action:', data.action)
      handleNewAction(data.action)
      break

    case 'message':
      console.log('New message:', data.message)
      showNotification(data.message)
      break
  }
}

ws.onerror = (error) => {
  console.error('WebSocket error:', error)
}

ws.onclose = () => {
  console.log('Disconnected from real-time bus')
  // Reconnect after delay
  setTimeout(() => connectWebSocket(), 5000)
}

10. Private Messaging

// Send a private message
const message = await api.action.post({
  type: 'MSG',
  subject: 'bob@example.com',
  content: {
    text: 'Hey Bob, how are you?',
    subject: 'Checking in'
  }
})

console.log('Message sent:', message.data.actionId)

// Get your messages
const messages = await api.action.get({
  type: 'MSG',
  involved: cloudillo.idTag,
  _expand: 'issuer,subject',
  _sort: 'createdAt',
  _order: 'desc',
  _limit: 50
})

console.log(`You have ${messages.data.length} messages`)

// Display messages
messages.data.forEach(msg => {
  const from = msg.issuerTag === cloudillo.idTag ? 'You' : msg.issuer.name
  const to = msg.subject === cloudillo.idTag ? 'You' : msg.subject
  console.log(`${from}${to}: ${msg.content.text}`)
})

11. Search and Filter

Search Posts by Text

// Get posts from the last week
const lastWeek = Math.floor(Date.now() / 1000) - 7 * 24 * 60 * 60

const recentPosts = await api.action.get({
  type: 'POST',
  status: 'A',
  createdAfter: lastWeek,
  _expand: 'issuer',
  _limit: 100
})

// Filter in client (for complex searches)
const searchTerm = 'cloudillo'
const matchingPosts = recentPosts.data.filter(post =>
  post.content.text?.toLowerCase().includes(searchTerm)
)

console.log(`Found ${matchingPosts.length} posts mentioning "${searchTerm}"`)

Get User’s Activity

// Get everything involving a specific user
const userActivity = await api.action.get({
  involved: 'bob@example.com',
  _expand: 'issuer',
  _limit: 100,
  _sort: 'createdAt',
  _order: 'desc'
})

// Categorize by type
const byType = userActivity.data.reduce((acc, action) => {
  acc[action.type] = (acc[action.type] || 0) + 1
  return acc
}, {})

console.log('Activity breakdown:', byType)
// { POST: 15, CMNT: 32, REACT: 48, FLLW: 5 }

12. Share Files

// Share a file with another user
const share = await api.action.post({
  type: 'FSHR',
  subject: 'bob@example.com',
  attachments: ['b1~abc123'],
  content: {
    permission: 'READ',
    message: 'Check out this document!'
  }
})

console.log('File shared:', share.data.actionId)

// Share with write permission
const shareWrite = await api.action.post({
  type: 'FSHR',
  subject: 'carol@example.com',
  attachments: ['f1~xyz789'],
  content: {
    permission: 'WRITE',
    message: 'Let\'s collaborate on this!'
  }
})

13. Update Profile

// Update your profile information
const updated = await api.me.patch({
  name: 'Alice Johnson-Smith',
  x: {
    bio: 'Software developer and Cloudillo enthusiast',
    location: 'San Francisco, CA',
    website: 'https://alice.example.com',
    twitter: '@alice'
  }
})

console.log('Profile updated:', updated.data)

14. Handle Errors Gracefully

async function createPostSafely(content) {
  try {
    const post = await api.action.post({
      type: 'POST',
      content
    })

    console.log('✓ Post created:', post.data.actionId)
    return post.data

  } catch (error) {
    if (error.response) {
      const { code, message } = error.response.data.error

      switch (code) {
        case 'E-AUTH-EXPIRED':
          console.error('Session expired. Please login again.')
          window.location.href = '/login'
          break

        case 'E-PERM-DENIED':
          console.error('Permission denied:', message)
          alert('You don\'t have permission to post')
          break

        case 'E-RATE-LIMIT':
          console.error('Rate limited. Please slow down.')
          alert('Too many posts. Please wait a moment.')
          break

        default:
          console.error('Error creating post:', message)
          alert('Failed to create post. Please try again.')
      }
    } else {
      console.error('Network error:', error.message)
      alert('Network error. Check your connection.')
    }

    throw error
  }
}

// Usage
await createPostSafely({
  text: 'Hello, world!',
  title: 'My Post'
})

15. Pagination

async function getAllPosts() {
  const allPosts = []
  let offset = 0
  const limit = 50

  while (true) {
    const response = await api.action.get({
      type: 'POST',
      status: 'A',
      _limit: limit,
      _offset: offset,
      _expand: 'issuer'
    })

    allPosts.push(...response.data)

    console.log(`Loaded ${allPosts.length} of ${response.pagination.total} posts`)

    if (!response.pagination.hasMore) {
      break
    }

    offset += limit
  }

  return allPosts
}

// Load all posts (be careful with large datasets!)
const posts = await getAllPosts()
console.log(`Total posts loaded: ${posts.length}`)

Complete Example: Simple Social Feed

import * as cloudillo from '@cloudillo/base'

class SocialFeed {
  constructor(token) {
    this.api = cloudillo.createApiClient({ token })
  }

  async initialize() {
    // Get user profile
    const profile = await this.api.me.get()
    console.log('Logged in as:', profile.data.name)
  }

  async loadFeed(limit = 20) {
    // Get recent posts with full profiles
    const posts = await this.api.action.get({
      type: 'POST',
      status: 'A',
      _limit: limit,
      _expand: 'issuer',
      _sort: 'createdAt',
      _order: 'desc'
    })

    return posts.data
  }

  async createPost(text, title) {
    const post = await this.api.action.post({
      type: 'POST',
      content: { text, title }
    })

    console.log('✓ Post created')
    return post.data
  }

  async addComment(postId, text) {
    const comment = await this.api.action.post({
      type: 'CMNT',
      parentId: postId,
      content: { text }
    })

    console.log('✓ Comment added')
    return comment.data
  }

  async addReaction(actionId, reactionType = 'LOVE') {
    const reaction = await this.api.action.id(actionId).reaction.post({
      type: reactionType
    })

    console.log('✓ Reaction added')
    return reaction.data
  }

  async getComments(postId) {
    const comments = await this.api.action.get({
      type: 'CMNT',
      parentId: postId,
      _expand: 'issuer',
      _sort: 'createdAt',
      _order: 'asc'
    })

    return comments.data
  }

  displayPost(post) {
    console.log('\n' + '='.repeat(60))
    console.log(`${post.issuer.name} (@${post.issuerTag})`)
    console.log(`${post.content.title}`)
    console.log(post.content.text)
    console.log(`❤️ ${post.stat?.reactions || 0} | 💬 ${post.stat?.comments || 0}`)
    console.log('='.repeat(60))
  }
}

// Usage
const feed = new SocialFeed(token)
await feed.initialize()

// Load and display feed
const posts = await feed.loadFeed(10)
posts.forEach(post => feed.displayPost(post))

// Create a new post
await feed.createPost(
  'Just discovered Cloudillo - it\'s amazing!',
  'First Impressions'
)

// Interact with a post
await feed.addReaction('act_post123', 'LOVE')
await feed.addComment('act_post123', 'Totally agree!')

// Get comments
const comments = await feed.getComments('act_post123')
console.log(`\nComments (${comments.length}):`)
comments.forEach(c => {
  console.log(`  ${c.issuer.name}: ${c.content.text}`)
})

Next Steps

Now that you know the basics, explore:

Tips for Success

  1. Always handle errors - Network issues happen, be prepared
  2. Use pagination - Don’t load thousands of items at once
  3. Expand wisely - Only expand what you need to reduce payload size
  4. Cache when possible - Store user profiles, settings locally
  5. Test incrementally - Start small, add features gradually
  6. Monitor rate limits - Respect the API limits
  7. Validate input - Check data before sending to API
  8. Log requests - Keep request IDs for debugging

Common Gotchas

Forgetting to add /api/ prefix

fetch('/action')  // Wrong!
fetch('/api/action')  // Correct

Not handling authentication expiry

// Always check for auth errors and redirect to login

Using wrong file upload endpoint

// Wrong: POST /file/upload
// Correct: POST /api/file/{preset}/{file_name}

Not expanding relations

// Inefficient - requires N+1 queries
const actions = await api.action.get({ type: 'POST' })
for (const action of actions.data) {
  const issuer = await api.profile.get({ idTag: action.issuerTag })
}

// Efficient - single query
const actions = await api.action.get({
  type: 'POST',
  _expand: 'issuer'
})

Happy coding! 🚀