Skip to main content

Complete Application Examples

Learn by building! These complete application examples show you how to build real-world apps with Cocobase.

Todo Application

A simple yet complete todo app with authentication, CRUD operations, and real-time sync.

Features

  • User authentication
  • Create, read, update, delete todos
  • Mark todos as complete
  • Real-time sync across devices
  • Filter by status
// lib/cocobase.ts
import { Cocobase } from "cocobase";

export const db = new Cocobase({
  apiKey: process.env.NEXT_PUBLIC_COCOBASE_API_KEY!,
});

// types/todo.ts
export interface Todo {
  id: string;
  title: string;
  completed: boolean;
  userId: string;
  createdAt: string;
}

// hooks/useTodos.ts
import { useState, useEffect } from 'react';
import { db } from '@/lib/cocobase';
import type { Todo } from '@/types/todo';

export function useTodos() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadTodos();

    // Real-time subscription
    const unsubscribe = db.watch('todos', (event) => {
      if (event.type === 'create') {
        setTodos(prev => [...prev, event.data as Todo]);
      } else if (event.type === 'update') {
        setTodos(prev => prev.map(t =>
          t.id === event.data.id ? event.data as Todo : t
        ));
      } else if (event.type === 'delete') {
        setTodos(prev => prev.filter(t => t.id !== event.documentId));
      }
    });

    return () => unsubscribe();
  }, []);

  const loadTodos = async () => {
    try {
      const data = await db.listDocuments<Todo>('todos', {
        sort: { createdAt: -1 }
      });
      setTodos(data);
    } finally {
      setLoading(false);
    }
  };

  const addTodo = async (title: string) => {
    const todo = await db.createDocument<Todo>('todos', {
      title,
      completed: false,
      userId: db.auth.getCurrentUser()?.id,
    });
    return todo;
  };

  const toggleTodo = async (id: string, completed: boolean) => {
    await db.updateDocument('todos', id, { completed });
  };

  const deleteTodo = async (id: string) => {
    await db.deleteDocument('todos', id);
  };

  return { todos, loading, addTodo, toggleTodo, deleteTodo };
}

// components/TodoList.tsx
import { useTodos } from '@/hooks/useTodos';

export function TodoList() {
  const { todos, loading, addTodo, toggleTodo, deleteTodo } = useTodos();
  const [newTodo, setNewTodo] = useState('');

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!newTodo.trim()) return;
    await addTodo(newTodo);
    setNewTodo('');
  };

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="What needs to be done?"
        />
        <button type="submit">Add</button>
      </form>

      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={(e) => toggleTodo(todo.id, e.target.checked)}
            />
            <span style={{
              textDecoration: todo.completed ? 'line-through' : 'none'
            }}>
              {todo.title}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

E-Commerce Application

A complete e-commerce app with products, cart, and checkout.

Features

  • Product catalog with search
  • Shopping cart
  • User orders
  • Inventory management
  • Real-time stock updates
// types/product.ts
export interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  stock: number;
  category: string;
  images: string[];
}

export interface CartItem {
  productId: string;
  quantity: number;
  product?: Product;
}

export interface Order {
  id: string;
  userId: string;
  items: CartItem[];
  total: number;
  status: 'pending' | 'processing' | 'shipped' | 'delivered';
  createdAt: string;
}

// hooks/useProducts.ts
export function useProducts(category?: string) {
  const [products, setProducts] = useState<Product[]>([]);

  useEffect(() => {
    loadProducts();
  }, [category]);

  const loadProducts = async () => {
    const filters = category ? { category: { $eq: category } } : {};
    const data = await db.listDocuments<Product>('products', {
      filters,
      sort: { createdAt: -1 }
    });
    setProducts(data);
  };

  const searchProducts = async (query: string) => {
    const data = await db.listDocuments<Product>('products', {
      filters: {
        name: { $contains: query }
      }
    });
    setProducts(data);
  };

  return { products, searchProducts };
}

// hooks/useCart.ts
export function useCart() {
  const [cart, setCart] = useState<CartItem[]>([]);

  const addToCart = async (productId: string, quantity: number = 1) => {
    const existing = cart.find(item => item.productId === productId);

    if (existing) {
      setCart(cart.map(item =>
        item.productId === productId
          ? { ...item, quantity: item.quantity + quantity }
          : item
      ));
    } else {
      setCart([...cart, { productId, quantity }]);
    }

    // Save to database
    await db.createDocument('cart_items', {
      userId: db.auth.getCurrentUser()?.id,
      productId,
      quantity
    });
  };

  const removeFromCart = async (productId: string) => {
    setCart(cart.filter(item => item.productId !== productId));

    // Delete from database
    const cartItem = await db.listDocuments('cart_items', {
      filters: {
        userId: { $eq: db.auth.getCurrentUser()?.id },
        productId: { $eq: productId }
      }
    });

    if (cartItem[0]) {
      await db.deleteDocument('cart_items', cartItem[0].id);
    }
  };

  const checkout = async () => {
    const user = db.auth.getCurrentUser();
    if (!user) throw new Error('Please login to checkout');

    // Calculate total
    const total = cart.reduce((sum, item) => {
      return sum + (item.product?.price || 0) * item.quantity;
    }, 0);

    // Create order
    const order = await db.createDocument<Order>('orders', {
      userId: user.id,
      items: cart,
      total,
      status: 'pending'
    });

    // Clear cart
    setCart([]);

    return order;
  };

  return { cart, addToCart, removeFromCart, checkout };
}

// components/ProductCard.tsx
export function ProductCard({ product }: { product: Product }) {
  const { addToCart } = useCart();

  return (
    <div className="product-card">
      <img src={product.images[0]} alt={product.name} />
      <h3>{product.name}</h3>
      <p>{product.description}</p>
      <p className="price">${product.price.toFixed(2)}</p>
      <p className="stock">
        {product.stock > 0 ? `${product.stock} in stock` : 'Out of stock'}
      </p>
      <button
        onClick={() => addToCart(product.id)}
        disabled={product.stock === 0}
      >
        Add to Cart
      </button>
    </div>
  );
}

// components/Cart.tsx
export function Cart() {
  const { cart, removeFromCart, checkout } = useCart();
  const [loading, setLoading] = useState(false);

  const handleCheckout = async () => {
    setLoading(true);
    try {
      const order = await checkout();
      alert(`Order placed! Order ID: ${order.id}`);
    } finally {
      setLoading(false);
    }
  };

  const total = cart.reduce((sum, item) =>
    sum + (item.product?.price || 0) * item.quantity, 0
  );

  return (
    <div className="cart">
      <h2>Shopping Cart</h2>
      {cart.map(item => (
        <div key={item.productId} className="cart-item">
          <span>{item.product?.name}</span>
          <span>x{item.quantity}</span>
          <span>${((item.product?.price || 0) * item.quantity).toFixed(2)}</span>
          <button onClick={() => removeFromCart(item.productId)}>
            Remove
          </button>
        </div>
      ))}
      <div className="cart-total">
        <strong>Total: ${total.toFixed(2)}</strong>
      </div>
      <button onClick={handleCheckout} disabled={loading || cart.length === 0}>
        {loading ? 'Processing...' : 'Checkout'}
      </button>
    </div>
  );
}

Social Media Feed

A social media application with posts, likes, comments, and follows.

Features

  • Create and view posts
  • Like/unlike posts
  • Comment on posts
  • Follow/unfollow users
  • Real-time feed updates
  • User profiles
// types/social.ts
export interface Post {
  id: string;
  userId: string;
  user?: User;
  content: string;
  images?: string[];
  likes: string[];
  commentCount: number;
  createdAt: string;
}

export interface Comment {
  id: string;
  postId: string;
  userId: string;
  user?: User;
  content: string;
  createdAt: string;
}

export interface User {
  id: string;
  email: string;
  data: {
    full_name: string;
    avatar?: string;
    bio?: string;
  };
}

// hooks/useFeed.ts
export function useFeed() {
  const [posts, setPosts] = useState<Post[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    loadFeed();

    // Real-time updates
    const unsubscribe = db.watch('posts', (event) => {
      if (event.type === 'create') {
        setPosts(prev => [event.data as Post, ...prev]);
      } else if (event.type === 'update') {
        setPosts(prev => prev.map(p =>
          p.id === event.data.id ? event.data as Post : p
        ));
      } else if (event.type === 'delete') {
        setPosts(prev => prev.filter(p => p.id !== event.documentId));
      }
    });

    return () => unsubscribe();
  }, []);

  const loadFeed = async () => {
    try {
      const data = await db.listDocuments<Post>('posts', {
        sort: { createdAt: -1 },
        populate: ['userId'],
        limit: 20
      });
      setPosts(data);
    } finally {
      setLoading(false);
    }
  };

  const createPost = async (content: string, images?: string[]) => {
    const post = await db.createDocument<Post>('posts', {
      userId: db.auth.getCurrentUser()?.id,
      content,
      images: images || [],
      likes: [],
      commentCount: 0
    });
    return post;
  };

  const likePost = async (postId: string) => {
    const userId = db.auth.getCurrentUser()?.id;
    const post = posts.find(p => p.id === postId);

    if (!post || !userId) return;

    const likes = post.likes.includes(userId)
      ? post.likes.filter(id => id !== userId)
      : [...post.likes, userId];

    await db.updateDocument('posts', postId, { likes });
  };

  const deletePost = async (postId: string) => {
    await db.deleteDocument('posts', postId);
  };

  return { posts, loading, createPost, likePost, deletePost };
}

// hooks/useComments.ts
export function useComments(postId: string) {
  const [comments, setComments] = useState<Comment[]>([]);

  useEffect(() => {
    loadComments();
  }, [postId]);

  const loadComments = async () => {
    const data = await db.listDocuments<Comment>('comments', {
      filters: { postId: { $eq: postId } },
      sort: { createdAt: -1 },
      populate: ['userId']
    });
    setComments(data);
  };

  const addComment = async (content: string) => {
    const comment = await db.createDocument<Comment>('comments', {
      postId,
      userId: db.auth.getCurrentUser()?.id,
      content
    });

    // Increment comment count
    const post = await db.getDocument('posts', postId);
    await db.updateDocument('posts', postId, {
      commentCount: (post.commentCount || 0) + 1
    });

    return comment;
  };

  return { comments, addComment };
}

// components/PostCard.tsx
export function PostCard({ post }: { post: Post }) {
  const { likePost, deletePost } = useFeed();
  const currentUser = db.auth.getCurrentUser();
  const isLiked = post.likes.includes(currentUser?.id || '');

  return (
    <div className="post-card">
      <div className="post-header">
        <img src={post.user?.data.avatar} alt={post.user?.data.full_name} />
        <div>
          <h4>{post.user?.data.full_name}</h4>
          <span>{new Date(post.createdAt).toLocaleDateString()}</span>
        </div>
      </div>

      <p className="post-content">{post.content}</p>

      {post.images && post.images.length > 0 && (
        <div className="post-images">
          {post.images.map((img, i) => (
            <img key={i} src={img} alt="" />
          ))}
        </div>
      )}

      <div className="post-actions">
        <button onClick={() => likePost(post.id)}>
          {isLiked ? '❤️' : '🤍'} {post.likes.length}
        </button>
        <button>💬 {post.commentCount}</button>
        {currentUser?.id === post.userId && (
          <button onClick={() => deletePost(post.id)}>🗑️ Delete</button>
        )}
      </div>
    </div>
  );
}

// components/CreatePost.tsx
export function CreatePost() {
  const { createPost } = useFeed();
  const [content, setContent] = useState('');
  const [posting, setPosting] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!content.trim()) return;

    setPosting(true);
    try {
      await createPost(content);
      setContent('');
    } finally {
      setPosting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="create-post">
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="What's on your mind?"
        rows={3}
      />
      <button type="submit" disabled={posting || !content.trim()}>
        {posting ? 'Posting...' : 'Post'}
      </button>
    </form>
  );
}

Real-Time Chat Application

A complete chat app with rooms, direct messages, and online status.

Features

  • Real-time messaging
  • Chat rooms
  • Direct messages
  • Online status
  • Typing indicators
  • Message history
// hooks/useChat.ts
export function useChat(roomId: string) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [typing, setTyping] = useState<string[]>([]);

  useEffect(() => {
    // Load message history
    loadMessages();

    // Subscribe to new messages
    const unsubscribe = db.realtimeApi.joinRoom(roomId, (event) => {
      if (event.type === 'message') {
        setMessages(prev => [...prev, event.data]);
      } else if (event.type === 'typing') {
        setTyping(prev => [...prev, event.userId]);
        setTimeout(() => {
          setTyping(prev => prev.filter(id => id !== event.userId));
        }, 3000);
      }
    });

    return () => unsubscribe();
  }, [roomId]);

  const loadMessages = async () => {
    const data = await db.listDocuments('messages', {
      filters: { roomId: { $eq: roomId } },
      sort: { createdAt: 1 },
      populate: ['userId'],
      limit: 50
    });
    setMessages(data);
  };

  const sendMessage = async (content: string) => {
    const message = await db.createDocument('messages', {
      roomId,
      userId: db.auth.getCurrentUser()?.id,
      content
    });

    // Broadcast to room
    await db.realtimeApi.broadcast(roomId, {
      type: 'message',
      data: message
    });

    return message;
  };

  const sendTyping = async () => {
    await db.realtimeApi.broadcast(roomId, {
      type: 'typing',
      userId: db.auth.getCurrentUser()?.id
    });
  };

  return { messages, typing, sendMessage, sendTyping };
}

// components/ChatRoom.tsx
export function ChatRoom({ roomId }: { roomId: string }) {
  const { messages, typing, sendMessage, sendTyping } = useChat(roomId);
  const [input, setInput] = useState('');
  const messagesEndRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  const handleSend = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim()) return;

    await sendMessage(input);
    setInput('');
  };

  const handleTyping = () => {
    sendTyping();
  };

  return (
    <div className="chat-room">
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id} className="message">
            <strong>{msg.user?.data.full_name}:</strong>
            <span>{msg.content}</span>
            <small>{new Date(msg.createdAt).toLocaleTimeString()}</small>
          </div>
        ))}
        {typing.length > 0 && (
          <div className="typing-indicator">
            Someone is typing...
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>

      <form onSubmit={handleSend} className="message-input">
        <input
          value={input}
          onChange={(e) => {
            setInput(e.target.value);
            handleTyping();
          }}
          placeholder="Type a message..."
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

Notes App with Real-Time Sync

A collaborative notes app with folders and real-time synchronization.

Features

  • Create and organize notes in folders
  • Rich text editing
  • Real-time sync across devices
  • Collaboration with other users
  • Search notes
// Full implementation available in the examples repository

Complete App Architecture

All these apps follow this general architecture:
/src
  /lib
    cocobase.ts         # Cocobase client setup
  /types
    index.ts            # TypeScript interfaces
  /hooks
    useAuth.ts          # Authentication hook
    useData.ts          # Data fetching hooks
  /components
    ...                 # React components
  /pages or /screens
    ...                 # App pages/screens

Best Practices

  • Keep documents denormalized for read performance
  • Use relationships for one-to-many associations
  • Store frequently accessed data together
  • Only subscribe to data you’re actively displaying - Unsubscribe when components unmount - Filter subscriptions to reduce bandwidth
  • Always handle errors in async operations - Show user-friendly error messages
  • Implement retry logic for network errors
  • Paginate large lists
  • Cache frequently accessed data
  • Use optimistic UI updates

Source Code

Find complete source code for all these examples in our GitHub repository:

Cocobase Examples Repository

Full source code, setup instructions, and live demos

Next Steps

Deploy Your App

Learn how to deploy your Cocobase app to production

Best Practices

Optimize your app for performance and security