Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rjdellecese/confect/llms.txt

Use this file to discover all available pages before exploring further.

Quickstart

This guide will walk you through creating your first Confect application. You’ll build a simple notes app with a database schema, backend functions, and a React frontend - all with full type safety from end to end.

Prerequisites

Make sure you have Node.js 22+ and pnpm 10+ installed on your system.
You’ll also need:

Installation

1

Install Confect Packages

Install all required Confect packages along with their peer dependencies:
pnpm add @confect/core @confect/server @confect/react @confect/cli
pnpm add effect convex react react-dom
pnpm add -D @types/react @types/react-dom typescript
Confect requires Effect 3.19.16+, Convex 1.30.0+, and React 18 or 19.
2

Initialize Convex

If you haven’t already, initialize Convex in your project:
npx convex dev
This creates a convex/ directory and convex.json configuration file.
3

Configure Convex Code Generation

Update your convex.json to enable static API and data model generation:
convex.json
{
  "codegen": {
    "staticApi": true,
    "staticDataModel": true
  }
}
4

Create Confect Directory Structure

Create a confect/ directory alongside your convex/ directory:
mkdir confect
mkdir confect/tables
mkdir confect/spec
mkdir confect/impl

Define Your Database Schema

Create your first table using Effect Schema:
import { Table } from "@confect/server";
import { Schema } from "effect";

export const Notes = Table.make(
  "notes",
  Schema.Struct({
    text: Schema.String.pipe(Schema.maxLength(100)),
    tag: Schema.optional(Schema.String),
  })
)
  .index("by_text", ["text"]);
The schema uses Effect’s powerful validation - maxLength(100) ensures notes are never too long, and it’s validated at runtime automatically!

Define Your Function Specifications

Specify the API for your backend functions:
import { FunctionSpec, GroupSpec, GenericId } from "@confect/core";
import { Schema } from "effect";
import { Notes } from "../tables/Notes";

export const notes = GroupSpec.make("notes")
  .addFunction(
    FunctionSpec.publicMutation({
      name: "insert",
      args: Schema.Struct({ text: Schema.String }),
      returns: GenericId.GenericId("notes"),
    })
  )
  .addFunction(
    FunctionSpec.publicQuery({
      name: "list",
      args: Schema.Struct({}),
      returns: Schema.Array(Notes.Doc),
    })
  )
  .addFunction(
    FunctionSpec.publicMutation({
      name: "delete_",
      args: Schema.Struct({ noteId: GenericId.GenericId("notes") }),
      returns: Schema.Null,
    })
  );
FunctionSpec defines the contract for your functions - their names, arguments, return types, and visibility (public/internal).

Implement Your Functions

Write the actual business logic using Effect and Confect services:
import { FunctionImpl, GroupImpl } from "@confect/server";
import { Effect, Layer } from "effect";
import api from "../_generated/api";
import { DatabaseReader, DatabaseWriter } from "../_generated/services";

const insert = FunctionImpl.make(
  api,
  "notes",
  "insert",
  ({ text }) =>
    Effect.gen(function* () {
      const writer = yield* DatabaseWriter;
      return yield* writer.table("notes").insert({ text });
    }).pipe(Effect.orDie)
);

const list = FunctionImpl.make(
  api,
  "notes",
  "list",
  () =>
    Effect.gen(function* () {
      const reader = yield* DatabaseReader;
      return yield* reader
        .table("notes")
        .index("by_creation_time", "desc")
        .collect();
    }).pipe(Effect.orDie)
);

const delete_ = FunctionImpl.make(
  api,
  "notes",
  "delete_",
  ({ noteId }) =>
    Effect.gen(function* () {
      const writer = yield* DatabaseWriter;
      yield* writer.table("notes").delete(noteId);
      return null;
    }).pipe(Effect.orDie)
);

export const notes = GroupImpl.make(api, "notes").pipe(
  Layer.provide(insert),
  Layer.provide(list),
  Layer.provide(delete_)
);
DatabaseReader and DatabaseWriter are Effect services that provide type-safe access to your Convex database with automatic schema validation.

Generate Code

Run the Confect CLI to generate TypeScript types and React hooks:
pnpx confect dev
This generates:
  • confect/_generated/api.ts - API references for your functions
  • confect/_generated/refs.ts - Type-safe references for React hooks
  • confect/_generated/services.ts - Effect services for database access
  • And more!
Keep confect dev running during development - it watches for changes and regenerates types automatically.

Build Your React Frontend

Use the generated hooks in your React components:
tsx src/App.tsx
import { useQuery, useMutation } from "@confect/react";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import { Array } from "effect";
import { useState } from "react";
import refs from "../confect/_generated/refs";

const App = () => {
  const convexClient = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

  return (
    <ConvexProvider client={convexClient}>
      <NotesApp />
    </ConvexProvider>
  );
};

const NotesApp = () => {
  const [note, setNote] = useState("");
  
  // Fully typed query - autocomplete and type checking!
  const notes = useQuery(refs.public.notes.list, {});
  
  // Fully typed mutation
  const insertNote = useMutation(refs.public.notes.insert);
  const deleteNote = useMutation(refs.public.notes.delete_);

  const handleInsert = () => {
    void insertNote({ text: note }).then(() => setNote(""));
  };

  if (notes === undefined) {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <h1>My Notes</h1>
      
      <div>
        <textarea
          rows={4}
          cols={50}
          value={note}
          onChange={(e) => setNote(e.target.value)}
          placeholder="Enter your note (max 100 chars)"
        />
        <br />
        <button onClick={handleInsert}>Add Note</button>
      </div>

      <ul>
        {Array.map(notes, (note) => (
          <li key={note._id}>
            <p>{note.text}</p>
            <button onClick={() => void deleteNote({ noteId: note._id })}>
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;
Notice how refs.public.notes.list provides full autocomplete and type checking - if you try to pass wrong arguments, TypeScript will catch it!

Run Your Application

Start your development servers:
pnpm concurrently \
  --prefix-colors='cyan,yellow,green' \
  --names='VITE,CONVEX,CONFECT' \
  'vite' \
  'convex dev --tail-logs' \
  'confect dev'
The all-in-one command requires concurrently installed: pnpm add -D concurrently
Your application should now be running at http://localhost:5173!

What You’ve Built

Congratulations! You’ve created a fully type-safe notes application with:

Schema Validation

Effect Schema validates all data at runtime and compile-time

Type-Safe API

Full TypeScript types from database to React components

Effect Services

Composable, testable business logic using Effect patterns

Real-Time Updates

Automatic UI updates via Convex’s real-time infrastructure

Next Steps

Schema & Tables

Learn about advanced schema features like indexes and vector search

Functions

Explore queries, mutations, actions, and HTTP APIs

Services

Use Effect services for database access and platform capabilities

React Integration

Master the React hooks and client-side patterns