> ## Documentation Index
> Fetch the complete documentation index at: https://confect.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Install Confect and build a type-safe Convex app with Effect schemas in minutes.

## Installation

<CodeGroup>
  ```bash pnpm theme={null}
  pnpm add convex @confect/core @confect/server @confect/cli @confect/react
  ```

  ```bash npm theme={null}
  npm install convex @confect/core @confect/server @confect/cli @confect/react
  ```

  ```bash yarn theme={null}
  yarn add convex @confect/core @confect/server @confect/cli @confect/react
  ```

  ```bash bun theme={null}
  bun add convex @confect/core @confect/server @confect/cli @confect/react
  ```
</CodeGroup>

## Usage

<Steps>
  <Step title="Set up your Convex dev deployment">
    Run `convex dev` to set up your Convex dev deployment.
  </Step>

  <Step title="Define your database schema">
    <Note>
      Not every Effect `Schema` is valid for use in Confect. See
      [Schema Restrictions](/concepts/schema-restrictions) for more
      information about what's permitted and what's not.
    </Note>

    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 `Schema`s (`notes.Doc`, `notes.Fields`).

    ```ts confect/tables/notes.ts theme={null}
    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.
  </Step>

  <Step title="Define your Confect API spec">
    Create a `GroupSpec` and add some functions to it.

    ```ts confect/notes.spec.ts theme={null}
    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"),
        }),
      );
    ```
  </Step>

  <Step title="Run the codegen command">
    Generate your app's `confect/_generated/` files.

    ```bash theme={null}
    confect codegen
    ```
  </Step>

  <Step title="Implement your Confect functions">
    Create a `GroupImpl` and implement its functions.

    ```ts confect/notes.impl.ts theme={null}
    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.
  </Step>

  <Step title="Start your Confect app">
    Run the `confect dev` command to generate your app's Convex functions.

    ```bash theme={null}
    confect dev
    ```

    In another terminal, run the `convex dev` command to start your Convex dev deployment.

    ```bash theme={null}
    convex dev
    ```
  </Step>

  <Step title="Call your Confect functions from your React app">
    Set up the Convex React client and provider.

    ```tsx src/main.tsx theme={null}
    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.

    ```tsx src/App.tsx theme={null}
    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;
    ```
  </Step>
</Steps>

## Example project

See a full, working example project [here](https://github.com/rjdellecese/confect/tree/main/apps/example).
