Skip to main content
Your Confect API is broken up into two parts: a spec and its implementation (or impl).

Spec

A spec defines the interface of your Confect API. It is made up of group specs and function specs. Each function spec defines the function’s name, arguments schema, and returns schema — but not the function’s logic. Specs are built using @confect/core, which means they can be shared between the server and client. This separation is what enables end-to-end schema decoding and encoding: the client knows the exact shape of every function’s arguments and return value without importing any server code.
confect/notes.spec.ts
import { GroupSpec, FunctionSpec } from "@confect/core";
import { Schema } from "effect";

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

const list = FunctionSpec.publicQuery({
  name: "list",
  args: Schema.Struct({}),
  returns: Schema.Array(Notes.Doc),
});

export const notes = GroupSpec.make("notes").addFunction(list);
The .spec.ts suffix used here is a naming convention, not a requirement. Only the entry-point files (spec.ts and impl.ts) have fixed names. Your spec must be the default export of a spec.ts file in your confect/ directory.
confect/spec.ts
import { Spec } from "@confect/core";

import { notes } from "./notes.spec";

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

Impl

An impl provides the logic for each function declared in your spec. It is built using Effect layers: each function impl is a Layer that is composed into a group impl, and group impls are composed into the top-level impl.
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),
);

export const notes = GroupImpl.make(api, "notes").pipe(
  Layer.provide(list),
);
Your impl must be the default export of an impl.ts file in your confect/ directory, and must be finalized with Impl.finalize.
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,
);