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>
)
}