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.
confect/tables/Notes.ts
import { Table } from "@confect/server";
import { Schema } from "effect";

export const Notes = Table.make(
  "notes",
  Schema.Struct({
    text: Schema.String,
  }),
);
And then add it to your database schema.
confect/schema.ts
import { DatabaseSchema } from "@confect/server";

import { Notes } from "./tables/Notes";

export default DatabaseSchema.make().addTable(Notes);
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 { Schema } from "effect";

import { Notes } from "./tables/Notes";

export const notes = GroupSpec.make("notes")
  .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: Schema.Null,
    }),
  );
Add the GroupSpec to your API’s Spec.
confect/spec.ts
import { Spec } from "@confect/core";
import { notes } from "./notes.spec";

export default Spec.make().add(notes);
4

Run the codegen command

Generate your app’s Confect API, services, and registered functions.
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 { Effect, Layer } from "effect";

import api from "./_generated/api";
import { DatabaseReader, DatabaseWriter } from "./_generated/services";

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 create = FunctionImpl.make(api, "notes", "create", ({ text }) =>
  Effect.gen(function* () {
    const writer = yield* DatabaseWriter;

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

export const notes = GroupImpl.make(api, "notes").pipe(
  Layer.provide(list),
  Layer.provide(create),
);
Add the GroupImpl to your API’s Impl, and finalize it.
confect/impl.ts
import { Impl } from "@confect/server";
import { Layer } from "effect";

import api from "./_generated/api";
import { notes } from "./notes.impl";

export default Impl.make(api).pipe(
  Layer.provide(notes),
  Impl.finalize,
);
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 { useMutation, useQuery } from "@confect/react";
import { Array } from "effect";
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>
        {notes === undefined
          ? <p>Loading…</p>
          : 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.