> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cubby.pro/llms.txt
> Use this file to discover all available pages before exploring further.

# Building Your First App

> Step-by-step guide to creating and deploying a Cubby app

# Building Your First App

Let's build a complete todo list app from scratch and deploy it to Cubby.

## What We'll Build

A simple todo app with:

* Add new todos
* Mark todos complete
* Delete todos
* Data persisted in Cubby's default SQLite database
* User-specific data (each user sees only their todos)

**Time:** \~15 minutes

## Prerequisites

* Node.js 18 or later
* Docker Desktop (for local development)
* Cubby account and CLI installed

```bash theme={null}
npm install -g cubbypro
cubby login
```

## Step 1: Create the Project

```bash theme={null}
cubby init my-todo-app
cd my-todo-app
npm install
```

This creates a Next.js + Prisma project configured for Cubby's default SQLite path.

## Step 2: Define the Data Model

Edit `prisma/schema.prisma` to add a Todo model:

```prisma theme={null}
model Todo {
  id        String   @id @default(cuid())
  title     String
  completed Boolean  @default(false)
  userId    String   // Owner's Cubby user ID
  createdAt DateTime @default(now())
}

// Keep the Example model or delete it
model Example {
  id        String   @id @default(cuid())
  name      String
  createdAt DateTime @default(now())
}
```

## Step 3: Create the API Routes

### List and Create Todos

Create `app/api/todos/route.ts`:

```typescript theme={null}
import { headers } from 'next/headers'
import { prisma } from '@/lib/db'
import { NextRequest, NextResponse } from 'next/server'

// GET /api/todos - List user's todos
export async function GET() {
  const h = await headers()
  const userId = h.get('X-Cubby-User-Id')!

  const todos = await prisma.todo.findMany({
    where: { userId },
    orderBy: { createdAt: 'desc' }
  })

  return NextResponse.json(todos)
}

// POST /api/todos - Create a todo
export async function POST(request: NextRequest) {
  const h = await headers()
  const userId = h.get('X-Cubby-User-Id')!
  const { title } = await request.json()

  if (!title || typeof title !== 'string') {
    return NextResponse.json({ error: 'Title required' }, { status: 400 })
  }

  const todo = await prisma.todo.create({
    data: { title, userId }
  })

  return NextResponse.json(todo, { status: 201 })
}
```

### Update and Delete Todos

Create `app/api/todos/[id]/route.ts`:

```typescript theme={null}
import { headers } from 'next/headers'
import { prisma } from '@/lib/db'
import { NextRequest, NextResponse } from 'next/server'

type Params = { params: Promise<{ id: string }> }

// PUT /api/todos/[id] - Update a todo
export async function PUT(request: NextRequest, { params }: Params) {
  const h = await headers()
  const userId = h.get('X-Cubby-User-Id')!
  const { id } = await params
  const { completed } = await request.json()

  // Verify ownership
  const existing = await prisma.todo.findFirst({
    where: { id, userId }
  })
  if (!existing) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 })
  }

  const todo = await prisma.todo.update({
    where: { id },
    data: { completed }
  })

  return NextResponse.json(todo)
}

// DELETE /api/todos/[id] - Delete a todo
export async function DELETE(request: NextRequest, { params }: Params) {
  const h = await headers()
  const userId = h.get('X-Cubby-User-Id')!
  const { id } = await params

  // Verify ownership
  const existing = await prisma.todo.findFirst({
    where: { id, userId }
  })
  if (!existing) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 })
  }

  await prisma.todo.delete({ where: { id } })
  return NextResponse.json({ deleted: true })
}
```

## Step 4: Build the UI

Replace `app/page.tsx` with:

```typescript theme={null}
'use client'

import { useState, useEffect } from 'react'

export const dynamic = 'force-dynamic'

interface Todo {
  id: string
  title: string
  completed: boolean
  createdAt: string
}

export default function Home() {
  const [todos, setTodos] = useState<Todo[]>([])
  const [newTodo, setNewTodo] = useState('')

  // Load todos on mount
  useEffect(() => {
    fetch('/api/todos')
      .then(res => res.json())
      .then(setTodos)
  }, [])

  // Add a new todo
  async function addTodo(e: React.FormEvent) {
    e.preventDefault()
    if (!newTodo.trim()) return

    const res = await fetch('/api/todos', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title: newTodo })
    })
    const todo = await res.json()
    setTodos([todo, ...todos])
    setNewTodo('')
  }

  // Toggle completion
  async function toggleTodo(id: string, completed: boolean) {
    await fetch(`/api/todos/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ completed: !completed })
    })
    setTodos(todos.map(t =>
      t.id === id ? { ...t, completed: !completed } : t
    ))
  }

  // Delete a todo
  async function deleteTodo(id: string) {
    await fetch(`/api/todos/${id}`, { method: 'DELETE' })
    setTodos(todos.filter(t => t.id !== id))
  }

  return (
    <main style={{ maxWidth: 600, margin: '0 auto', padding: 20 }}>
      <h1>My Todos</h1>

      <form onSubmit={addTodo} style={{ marginBottom: 20 }}>
        <input
          value={newTodo}
          onChange={e => setNewTodo(e.target.value)}
          placeholder="What needs to be done?"
          style={{ padding: 10, width: '70%', marginRight: 10 }}
        />
        <button type="submit" style={{ padding: '10px 20px' }}>
          Add
        </button>
      </form>

      <ul style={{ listStyle: 'none', padding: 0 }}>
        {todos.map(todo => (
          <li
            key={todo.id}
            style={{
              display: 'flex',
              alignItems: 'center',
              padding: 10,
              borderBottom: '1px solid #eee'
            }}
          >
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id, todo.completed)}
              style={{ marginRight: 10 }}
            />
            <span style={{
              flex: 1,
              textDecoration: todo.completed ? 'line-through' : 'none',
              color: todo.completed ? '#999' : 'inherit'
            }}>
              {todo.title}
            </span>
            <button
              onClick={() => deleteTodo(todo.id)}
              style={{ color: 'red', border: 'none', cursor: 'pointer' }}
            >
              Delete
            </button>
          </li>
        ))}
      </ul>

      {todos.length === 0 && (
        <p style={{ color: '#999', textAlign: 'center' }}>
          No todos yet. Add one above!
        </p>
      )}
    </main>
  )
}
```

## Step 5: Test Locally

Start the development environment:

```bash theme={null}
cubby dev
```

Open [http://localhost:3000](http://localhost:3000) and test:

* Add a few todos
* Mark some complete
* Delete one

Data persists between page refreshes because it's stored in the local SQLite database.

## Step 6: Deploy to Production

```bash theme={null}
cubby deploy
```

The CLI will:

1. Package your code
2. Run platform checks
3. Build a Docker image
4. Inject the production `DATABASE_URL`
5. Start your container and route traffic to it

When complete, you'll see:

```
Your app is live at:
https://my-todo-app--yourname.cubby.pro
```

## Step 7: Test in Production

Visit your app URL. You'll be prompted to log in with your Cubby account. After login:

* Add some todos
* Refresh the page - data persists
* Share the app with a friend (they'll see their own todos, not yours)

## What's Next?

* **Add a secret**: Try integrating an API like OpenAI
  ```bash theme={null}
  cubby secrets set OPENAI_API_KEY --env prod
  cubby deploy
  ```

* **Share your app**: From the dashboard, share with other Cubby users

* **Add more features**: Due dates, categories, search

## Troubleshooting

### "Build failed"

Run `cubby check` to see what's wrong:

```bash theme={null}
cubby check
```

### "Not logged in"

```bash theme={null}
cubby login
```

### Database Errors

Reset your local database and try again:

```bash theme={null}
cubby dev --reset
```
