Skip to main content

Installation

pnpm add convex @confect/core @confect/server @confect/cli @confect/react

Usage

1

Set up your Convex dev deployment

Run convex dev to set up your Convex dev deployment.
2

Define your database schema

Not every Effect Schema is valid for use in Confect. See Schema Restrictions for more information about what’s permitted and what’s not.
Define a table — one file per table under confect/tables/, where the filename is the table name (so filenames must be valid JS identifiers and may not start with _). Each file must default-export a Table; codegen reads module.default and applies the filename as the table name. From other modules (e.g. specs), default-import the codegen-emitted wrapper at confect/_generated/tables/<name> to reach the table’s derived Schemas (notes.Doc, notes.Fields).
confect/tables/notes.ts
import { Table } from "@confect/server";
import * as Schema from "effect/Schema";

export default Table.make(() =>
  Schema.Struct({
    text: Schema.String,
  }),
);
Codegen will assemble every confect/tables/*.ts into a DatabaseSchema (in confect/_generated/schema.ts) and a Convex deploy SchemaDefinition (in confect/_generated/convexSchema.ts) for you — you never write a confect/schema.ts. It also emits a confect/_generated/id.ts module exporting a type-safe Id constructor (Id("notes")) for cross-table references.
3

Define your Confect API spec

Create a GroupSpec and add some functions to it.
confect/notes.spec.ts
import { FunctionSpec, GroupSpec } from "@confect/core";
import * as Schema from "effect/Schema";

import { Id } from "./_generated/id";
import notes from "./_generated/tables/notes";

export default GroupSpec.make()
  .addFunction(
    FunctionSpec.publicQuery({
      name: "list",
      args: () => Schema.Struct({}),
      returns: () => Schema.Array(notes.Doc),
    }),
  )
  .addFunction(
    FunctionSpec.publicMutation({
      name: "create",
      args: () => Schema.Struct({ text: Schema.String }),
      returns: () => Id("notes"),
    }),
  );
4

Run the codegen command

Generate your app’s confect/_generated/ files.
confect codegen
5

Implement your Confect functions

Create a GroupImpl and implement its functions.
confect/notes.impl.ts
import { FunctionImpl, GroupImpl } from "@confect/server";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";

import databaseSchema from "./_generated/schema";
import { DatabaseReader, DatabaseWriter } from "./_generated/services";
import notes from "./notes.spec";

const list = FunctionImpl.make(databaseSchema, notes, "list", () =>
  Effect.gen(function* () {
    const reader = yield* DatabaseReader;

    return yield* reader
      .table("notes")
      .index("by_creation_time", "desc")
      .collect();
  }).pipe(Effect.orDie),
);

const create = FunctionImpl.make(databaseSchema, notes, "create", ({ text }) =>
  Effect.gen(function* () {
    const writer = yield* DatabaseWriter;

    return yield* writer.table("notes").insert({ text });
  }).pipe(Effect.orDie),
);

export default GroupImpl.make(databaseSchema, notes).pipe(
  Layer.provide(list),
  Layer.provide(create),
  GroupImpl.finalize,
);
GroupImpl.finalize only typechecks once every function declared by the spec has a corresponding FunctionImpl, so it acts as a per-group completeness check.Run confect codegen again to pick up the new impl.
6

Start your Confect app

Run the confect dev command to generate your app’s Convex functions.
confect dev
In another terminal, run the convex dev command to start your Convex dev deployment.
convex dev
7

Call your Confect functions from your React app

Set up the Convex React client and provider.
src/main.tsx
import { ConvexProvider, ConvexReactClient } from "convex/react";
import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App";

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

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <ConvexProvider client={convexClient}>
      <App />
    </ConvexProvider>
  </React.StrictMode>,
);
Use Confect’s React hooks alongside your Confect public refs to call your Confect functions.
src/App.tsx
import { QueryResult, useMutation, useQuery } from "@confect/react";
import * as Array from "effect/Array";
import { useState } from "react";

import refs from "../confect/_generated/refs";

const App = () => {
  const notes = useQuery(refs.public.notes.list, {});

  const createNote = useMutation(refs.public.notes.create);

  const [newNote, setNewNote] = useState("");

  return (
    <div>
      <ul>
        {QueryResult.match(notes, {
          onLoading: () => <p>Loading…</p>,
          onSuccess: (notes) =>
            Array.map(notes, (note) => (
              <li key={note._id}>{note.text}</li>
            )),
        })}
      </ul>

      <br />

      <textarea
        rows={4}
        cols={50}
        value={newNote}
        onChange={(e) => setNewNote(e.target.value)}
      />
      <br />
      <button
        onClick={() =>
          void createNote({ text: newNote }).then(() => setNewNote(""))
        }
      >
        Create note
      </button>
    </div>
  );
};

export default App;

Example project

See a full, working example project here.