.do
DeployCode Deployment

Deploy as Apps

Build interactive web applications with MDX Functions & Components

Deploy MDX Functions as Apps

Documentation Status: This documentation describes the planned API design for the .do platform. Code examples represent the intended interface and may not reflect the current implementation state. See roadmap for implementation status.

Build full-stack interactive web applications combining MDX components for UI and MDX functions for backend logic.

Overview

Deploy MDX as apps to create:

  • SaaS Dashboards - Full-featured business applications
  • Admin Panels - Internal management tools
  • Customer Portals - Self-service user interfaces
  • Internal Tools - Custom business applications
flowchart TB A[MDX Components & Functions] --> B[Full-Stack App] B --> C[Frontend] B --> D[Backend] C --> E[UI Components] C --> F[State Management] C --> G[Routing] D --> H[API Functions] D --> I[Database] D --> J[Authentication] E --> K[Deployed App] F --> K G --> K H --> K I --> K J --> K

Full-Stack App Example

---
title: Task Management App
description: Complete task management application
---

import { useState, useEffect } from 'react'

// Backend Functions
export async function getTasks(userId: string) {
return await $.db.tasks.find({
where: { userId },
orderBy: { createdAt: 'desc' }
})
}

export async function createTask(userId: string, data: { title: string; description?: string }) {
  return await $.db.tasks.create({
    ...data,
    userId,
    status: 'pending',
    createdAt: new Date()
  })
}

export async function updateTask(id: string, updates: Partial<Task>) {
  return await $.db.tasks.update(id, updates)
}

export async function deleteTask(id: string) {
  return await $.db.tasks.delete(id)
}

// Frontend Components
export function TaskList({ userId }) {
const [tasks, setTasks] = useState([])
const [loading, setLoading] = useState(true)

useEffect(() => {
loadTasks()
}, [userId])

async function loadTasks() {
setLoading(true)
const data = await getTasks(userId)
setTasks(data)
setLoading(false)
}

async function handleComplete(taskId) {
await updateTask(taskId, { status: 'completed' })
await loadTasks()
}

async function handleDelete(taskId) {
await deleteTask(taskId)
await loadTasks()
}

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

return (

<div className="task-list">
  {tasks.map((task) => (
    <TaskItem key={task.id} task={task} onComplete={handleComplete} onDelete={handleDelete} />
  ))}
</div>
) }

export function TaskItem({ task, onComplete, onDelete }) {
  return (
    <div className={`task-item ${task.status}`}>
      <div className="task-content">
        <h3>{task.title}</h3>
        {task.description && <p>{task.description}</p>}
      </div>
      <div className="task-actions">
        {task.status === 'pending' && <button onClick={() => onComplete(task.id)}>Complete</button>}
        <button onClick={() => onDelete(task.id)}>Delete</button>
      </div>
    </div>
  )
}

export function CreateTaskForm({ userId, onTaskCreated }) {
  const [title, setTitle] = useState('')
  const [description, setDescription] = useState('')
  const [submitting, setSubmitting] = useState(false)

async function handleSubmit(e) {
e.preventDefault()
setSubmitting(true)

    try {
      await createTask(userId, { title, description })
      setTitle('')
      setDescription('')
      onTaskCreated?.()
    } catch (error) {
      console.error('Failed to create task:', error)
    } finally {
      setSubmitting(false)
    }

}

return (

<form onSubmit={handleSubmit} className="create-task-form">
  <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Task title" required />
  <textarea value={description} onChange={(e) => setDescription(e.target.value)} placeholder="Description (optional)" />
  <button type="submit" disabled={submitting}>
    {submitting ? 'Creating...' : 'Create Task'}
  </button>
</form>
) }

// Main App Component
export default function TaskApp() {
const user = $.auth.useUser()

if (!user) {
return <LoginPage />
}

return (

<div className="task-app">
  <header>
    <h1>My Tasks</h1>
    <UserMenu user={user} />
  </header>
  <main>
    <CreateTaskForm userId={user.id} />
    <TaskList userId={user.id} />
  </main>
</div>
) }

Real-Time Features

Live Updates

export function LiveTaskList({ userId }) {
  const [tasks, setTasks] = useState([])

useEffect(() => {
// Initial load
getTasks(userId).then(setTasks)

    // Subscribe to real-time updates
    const unsubscribe = $.subscribe(`tasks:${userId}`, (event) => {
      switch (event.type) {
        case 'task.created':
          setTasks(prev => [event.task, ...prev])
          break
        case 'task.updated':
          setTasks(prev => prev.map(t =>
            t.id === event.task.id ? event.task : t
          ))
          break
        case 'task.deleted':
          setTasks(prev => prev.filter(t => t.id !== event.taskId))
          break
      }
    })

    return unsubscribe

}, [userId])

return <TaskList tasks={tasks} />
}

State Management

Built-in State

export function Counter() {
  // Automatic state persistence
  const [count, setCount] = $.state.use('counter', 0)

return (

<div>
  <p>Count: {count}</p>
  <button onClick={() => setCount(count + 1)}>Increment</button>
</div>
) }

Global State

// Define global state
export const useAppState = $.state.create({
user: null,
theme: 'light',
notifications: []
})

export function App() {
  const { user, theme, setTheme } = useAppState()

return (

<div className={`app theme-${theme}`}>
  <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button>
  {user && <Dashboard user={user} />}
</div>
) }

Data Fetching

Automatic Data Loading

export function UserProfile({ userId }) {
  // Automatic loading, error handling, and caching
  const { data: user, loading, error, refetch } = $.data.use(() =>
    $.db.users.findById(userId)
  )

if (loading) return <Spinner />
if (error) return <ErrorMessage error={error} />
if (!user) return <NotFound />

return (

<div className="user-profile">
  <Avatar src={user.avatar} />
  <h2>{user.name}</h2>
  <p>{user.email}</p>
  <button onClick={refetch}>Refresh</button>
</div>
) }

Forms and Validation

import { z } from 'zod'

const ProfileSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
bio: z.string().max(500).optional()
})

export function ProfileForm({ user }) {
  const { register, handleSubmit, errors } = $.form.use({
    schema: ProfileSchema,
    defaultValues: user
  })

async function onSubmit(data) {
await $.db.users.update(user.id, data)
$.toast.success('Profile updated!')
}

return (

<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}

      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}

      <textarea {...register('bio')} />

      <button type="submit">Save</button>
    </form>

)
}

Routing

export default function App() {
  return (
    <$.Router>
      <Route path="/" component={HomePage} />
      <Route path="/dashboard" component={Dashboard} auth />
      <Route path="/users/:id" component={UserProfile} />
      <Route path="/settings" component={Settings} auth />
      <Route path="*" component={NotFound} />
    </$.Router>
  )

}

Authentication

export function LoginPage() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

async function handleLogin(e) {
e.preventDefault()

    const { user, token } = await $.auth.login({ email, password })

    // Automatic token management
    $.auth.setToken(token)

    // Navigate to dashboard
    $.router.navigate('/dashboard')

}

return (

<form onSubmit={handleLogin}>
  <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
  <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
  <button type="submit">Log In</button>
</form>
) }

// Protected route
export function Dashboard() {
const user = $.auth.useUser() // Returns null if not logged in

if (!user) {
return <Redirect to="/login" />
}

return <DashboardContent user={user} />
}

Deployment Configuration

// do.config.ts
export default {
  apps: [
    {
      name: 'task-app',
      source: './apps/tasks.mdx',
      deployment: {
        type: 'app',
        domain: 'tasks.example.do',
        ssr: true, // Server-side rendering
        ssg: false, // Static site generation
        routes: {
          '/': { prerender: true },
          '/dashboard': { auth: true },
          '/api/*': { type: 'api' },
        },
      },
      build: {
        target: 'es2020',
        minify: true,
        sourcemaps: true,
      },
      environment: {
        API_URL: process.env.API_URL,
        AUTH_SECRET: process.env.AUTH_SECRET,
      },
    },
  ],
}

Styling

CSS Modules

---
styles: ./styles.module.css
---

export function Card({ children }) {
  return <div className={styles.card}>{children}</div>

}

Tailwind CSS

export function Button({ children, onClick }) {
  return (
    <button
      className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
      onClick={onClick}
    >
      {children}
    </button>
  )

}
  • Sites - Deploy as static sites
  • APIs - API integration
    • Components - UI components
    • Functions - Backend functions