Skip to main content

Introduction

Lawn uses Convex as its backend platform, providing real-time queries, mutations, and actions. All backend functions are located in convex/ and are accessible through the auto-generated api object.

Architecture

The Convex backend is organized into functional modules:
  • Authentication - Clerk integration for user identity and team-based access control
  • Videos - Core video management (upload, metadata, workflow status)
  • Comments - Time-based commenting system with threading support
  • Teams - Team and member management with role-based permissions
  • Projects - Project organization within teams
  • Share Links - Secure video sharing with optional passwords and expiration

Function Types

Queries

Queries read data from the database and run reactively. They automatically re-run when underlying data changes.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function VideoList({ projectId }) {
  const videos = useQuery(api.videos.list, { projectId });
  return <div>{videos?.map(v => <VideoCard key={v._id} video={v} />)}</div>;
}

Mutations

Mutations modify data in the database. They’re transactional and atomic.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function CreateVideoButton({ projectId }) {
  const createVideo = useMutation(api.videos.create);
  
  const handleClick = async () => {
    const videoId = await createVideo({
      projectId,
      title: 'New Video',
      description: 'Video description'
    });
    console.log('Created:', videoId);
  };
  
  return <button onClick={handleClick}>Create Video</button>;
}

Actions

Actions can call external APIs and perform long-running operations. They’re used for integrations with services like Mux and S3.

Data Model

The Convex schema defines the following core tables:
  • teams - Team organizations
  • teamMembers - User memberships in teams
  • projects - Projects within teams
  • videos - Video files and metadata
  • comments - Video comments with timestamps
  • shareLinks - Shareable video links
  • shareAccessGrants - Temporary access tokens

Authentication

All API functions require authentication through Clerk. The backend validates user identity and checks permissions based on:
  • Team membership
  • Project access
  • Video access
  • Role hierarchy: owner > admin > member > viewer
// Authenticated automatically via Convex + Clerk
const user = await ctx.auth.getUserIdentity();

Error Handling

Convex functions throw errors that are automatically propagated to the client:
const mutation = useMutation(api.videos.update);

try {
  await mutation({ videoId, title: 'New Title' });
} catch (error) {
  console.error('Mutation failed:', error.message);
}

Type Safety

Convex automatically generates TypeScript types for all functions:
import { api } from '../convex/_generated/api';
import type { Id } from '../convex/_generated/dataModel';

// Types are inferred automatically
const videoId: Id<'videos'> = await createVideo({ ... });

Real-time Updates

Queries are reactive and update automatically when data changes:
// This component automatically re-renders when videos change
function LiveVideoList({ projectId }) {
  const videos = useQuery(api.videos.list, { projectId });
  // videos updates in real-time as they're added/modified
  return <div>{videos?.length} videos</div>;
}

Best Practices

  1. Use queries for reads - They’re reactive and optimized
  2. Batch mutations - Combine related updates in a single mutation
  3. Validate inputs - Use Convex validators (v.string(), v.id(), etc.)
  4. Handle loading states - Queries return undefined while loading
  5. Optimize queries - Use indexes defined in the schema

Next Steps

Build docs developers (and LLMs) love