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.

Confect allows you to expose your functions as HTTP endpoints using Effect’s HTTP API module. This enables you to call your backend from any HTTP client, with full OpenAPI documentation powered by Scalar.

Overview

While the React hooks are ideal for real-time web applications, the HTTP client is perfect for:
  • Server-side rendering (SSR)
  • Mobile applications
  • External integrations and webhooks
  • Serverless functions
  • CLI tools
  • Third-party API consumers
Confect HTTP endpoints are built on Effect’s HTTP API module, which automatically generates OpenAPI documentation.

Installation

For TypeScript/JavaScript clients:
npm install @effect/platform effect
For other languages, use any standard HTTP client.

Defining HTTP Endpoints

First, define your HTTP API using Effect’s HTTP API module:
confect/http/path-prefix.ts
import {
  HttpApi,
  HttpApiBuilder,
  HttpApiEndpoint,
  HttpApiGroup,
  OpenApi,
} from "@effect/platform";
import { Effect, Layer, Schema } from "effect";
import refs from "../_generated/refs";
import { QueryRunner } from "../_generated/services";
import { Notes } from "../tables/Notes";

// Define an API group
class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.get("getFirst", "/get-first")
      .annotate(OpenApi.Description, "Get the first note, if there is one.")
      .addSuccess(Schema.Option(Notes.Doc)),
  )
  .annotate(OpenApi.Title, "Notes")
  .annotate(OpenApi.Description, "Operations on notes.") {}

// Define the API
export class Api extends HttpApi.make("Api")
  .annotate(OpenApi.Title, "Confect Example")
  .annotate(
    OpenApi.Description,
    "An example API built with Confect."
  )
  .add(ApiGroup)
  .prefix("/path-prefix") {}

// Implement the API group
const ApiGroupLive = HttpApiBuilder.group(Api, "notes", (handlers) =>
  handlers.handle("getFirst", () =>
    Effect.gen(function* () {
      const runQuery = yield* QueryRunner;
      const firstNote = yield* runQuery(
        refs.public.notesAndRandom.notes.getFirst,
        {},
      );
      return firstNote;
    }).pipe(Effect.orDie),
  ),
);

// Provide the implementation
export const ApiLive = HttpApiBuilder.api(Api).pipe(
  Layer.provide(ApiGroupLive),
);

Export the HTTP Handler

convex/http.ts
import http from "../confect/http";

export default http;

Using the TypeScript Client

Effect provides a fully typed HTTP client that works seamlessly with your API definition.

Basic Setup

client.ts
import { FetchHttpClient, HttpApiClient } from "@effect/platform";
import { Effect } from "effect";
import { Api } from "./confect/http/path-prefix";

// Create the HTTP client
const ApiClient = HttpApiClient.make(Api, {
  baseUrl: "https://your-deployment.convex.site",
});

// Call an endpoint
const getFirst = ApiClient.pipe(
  Effect.andThen((client) => client.notes.getFirst()),
  Effect.scoped,
  Effect.provide(FetchHttpClient.layer),
);

// Run the effect
const result = await Effect.runPromise(getFirst);
console.log(result);

Complete React Example

HttpExample.tsx
import { FetchHttpClient, HttpApiClient } from "@effect/platform";
import { Effect, Exit } from "effect";
import { useState } from "react";
import { Api } from "../confect/http/path-prefix";

// Create the API client
const ApiClient = HttpApiClient.make(Api, {
  baseUrl: import.meta.env.VITE_CONVEX_URL.replace(
    "convex.cloud",
    "convex.site",
  ),
});

const getFirst = ApiClient.pipe(
  Effect.andThen((client) => client.notes.getFirst()),
  Effect.scoped,
  Effect.provide(FetchHttpClient.layer),
);

const HttpExample = () => {
  const [response, setResponse] = useState<Exit.Exit<any, any> | null>(null);

  const handleFetch = async () => {
    const exit = await Effect.runPromiseExit(getFirst);
    setResponse(exit);
  };

  return (
    <div>
      <button onClick={handleFetch}>
        GET /path-prefix/get-first
      </button>
      <pre>
        {response
          ? Exit.match(response, {
              onSuccess: (value) => JSON.stringify(value, null, 2),
              onFailure: (error) => JSON.stringify(error, null, 2),
            })
          : "No response yet"}
      </pre>
    </div>
  );
};

export default HttpExample;

Using fetch or cURL

You can call Confect HTTP endpoints with any HTTP client:

JavaScript/TypeScript

const response = await fetch(
  "https://your-deployment.convex.site/path-prefix/get-first",
  {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  }
);

const data = await response.json();
console.log(data);

cURL

curl https://your-deployment.convex.site/path-prefix/get-first \
  -H "Content-Type: application/json"

Python

import requests

response = requests.get(
    "https://your-deployment.convex.site/path-prefix/get-first",
    headers={"Content-Type": "application/json"},
)

data = response.json()
print(data)

Authentication

Add authentication headers to secure your endpoints:

API Key Authentication

const ApiClient = HttpApiClient.make(Api, {
  baseUrl: "https://your-deployment.convex.site",
  headers: {
    "Authorization": "Bearer your-api-key",
  },
});

JWT Authentication

const getAuthenticatedClient = (token: string) =>
  HttpApiClient.make(Api, {
    baseUrl: "https://your-deployment.convex.site",
    headers: {
      "Authorization": `Bearer ${token}`,
    },
  });

const client = getAuthenticatedClient(userToken);

Request/Response Patterns

GET Request

class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.get("list", "/notes")
      .addSuccess(Schema.Array(Notes.Doc))
  ) {}

// Client
const notes = await client.notes.list().pipe(
  Effect.scoped,
  Effect.provide(FetchHttpClient.layer),
  Effect.runPromise,
);

POST Request with Body

class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.post("create", "/notes")
      .setPayload(Schema.Struct({ text: Schema.String }))
      .addSuccess(Schema.String) // Returns note ID
  ) {}

// Client
const noteId = await client.notes.create({
  payload: { text: "New note" },
}).pipe(
  Effect.scoped,
  Effect.provide(FetchHttpClient.layer),
  Effect.runPromise,
);

PUT Request

class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.put("update", "/notes/:id")
      .setPath(Schema.Struct({ id: Schema.String }))
      .setPayload(Schema.Struct({ text: Schema.String }))
      .addSuccess(Schema.Null)
  ) {}

// Client
await client.notes.update({
  path: { id: "note123" },
  payload: { text: "Updated text" },
}).pipe(
  Effect.scoped,
  Effect.provide(FetchHttpClient.layer),
  Effect.runPromise,
);

DELETE Request

class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.del("delete", "/notes/:id")
      .setPath(Schema.Struct({ id: Schema.String }))
      .addSuccess(Schema.Null)
  ) {}

// Client
await client.notes.delete({
  path: { id: "note123" },
}).pipe(
  Effect.scoped,
  Effect.provide(FetchHttpClient.layer),
  Effect.runPromise,
);

Query Parameters

Add query parameters to your endpoints:
class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.get("search", "/notes/search")
      .setUrlParams(
        Schema.Struct({
          query: Schema.String,
          limit: Schema.optional(Schema.Number),
        })
      )
      .addSuccess(Schema.Array(Notes.Doc))
  ) {}

// Client
const results = await client.notes.search({
  urlParams: {
    query: "important",
    limit: 10,
  },
}).pipe(
  Effect.scoped,
  Effect.provide(FetchHttpClient.layer),
  Effect.runPromise,
);

Error Handling

Using Effect’s Exit Type

import { Effect, Exit } from "effect";

const exit = await Effect.runPromiseExit(getFirst);

if (Exit.isSuccess(exit)) {
  console.log("Success:", exit.value);
} else {
  console.error("Failure:", exit.cause);
}

Using Try-Catch

try {
  const result = await Effect.runPromise(getFirst);
  console.log("Success:", result);
} catch (error) {
  console.error("Failed:", error);
}

Custom Error Responses

class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.get("getById", "/notes/:id")
      .setPath(Schema.Struct({ id: Schema.String }))
      .addSuccess(Notes.Doc)
      .addError(404, Schema.Struct({ message: Schema.String }))
  ) {}

OpenAPI Documentation

Confect automatically generates interactive OpenAPI documentation using Scalar.

Access Documentation

Visit your Convex deployment’s HTTP endpoint in a browser:
https://your-deployment.convex.site
You’ll see:
  • Interactive API explorer
  • Request/response schemas
  • Try-it-out functionality
  • Authentication options
  • Code generation in multiple languages
The OpenAPI documentation is automatically generated from your Effect schemas and annotations.

Customize Documentation

Add rich descriptions using OpenAPI annotations:
class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.get("list", "/notes")
      .annotate(OpenApi.Title, "List all notes")
      .annotate(
        OpenApi.Description,
        `
Returns all notes in the database, sorted by creation time.

## Usage

This endpoint supports pagination via query parameters.

## Rate Limits

- 100 requests per minute
- 1000 requests per hour
      `
      )
      .addSuccess(Schema.Array(Notes.Doc))
  )
  .annotate(OpenApi.Title, "Notes API")
  .annotate(OpenApi.Description, "Manage notes in your application") {}

Base URL Configuration

Your Confect HTTP endpoints are available at:

Development

http://localhost:3000 (when running npx convex dev)

Production

https://your-deployment.convex.site

Get Your URL

Find your deployment URL in:
  1. Convex Dashboard: Settings → Deployment URL
  2. Environment variable: VITE_CONVEX_URL (replace .cloud with .site)
  3. Command line: npx convex dashboard
// Convert WebSocket URL to HTTP URL
const wsUrl = import.meta.env.VITE_CONVEX_URL;
// "https://amazing-animal-123.convex.cloud"

const httpUrl = wsUrl.replace("convex.cloud", "convex.site");
// "https://amazing-animal-123.convex.site"

Type Safety Benefits

Compile-time Validation

TypeScript validates request/response shapes at build time

Auto-completion

Full IDE support for endpoints, parameters, and response types

Schema Evolution

Changes to schemas automatically propagate to clients

OpenAPI Generation

Documentation stays in sync with implementation

Best Practices

1. Version Your API

export class Api extends HttpApi.make("Api")
  .add(ApiGroupV1)
  .prefix("/v1") {}

2. Use Semantic URLs

// Good
HttpApiEndpoint.get("getUserById", "/users/:id")
HttpApiEndpoint.post("createUser", "/users")

// Avoid
HttpApiEndpoint.get("getUser", "/get-user")
HttpApiEndpoint.post("newUser", "/new-user")

3. Document Everything

HttpApiEndpoint.post("create", "/notes")
  .annotate(OpenApi.Title, "Create a note")
  .annotate(OpenApi.Description, "Creates a new note in the database")
  .setPayload(Schema.Struct({ text: Schema.String }))

4. Handle Errors Gracefully

Effect.gen(function* () {
  const note = yield* findNote(id);
  if (!note) {
    return yield* Effect.fail({
      status: 404,
      message: "Note not found",
    });
  }
  return note;
})

Comparison: HTTP vs React Hooks

React Hooks

Best for:
  • Real-time web applications
  • Automatic UI updates
  • WebSocket connections
  • React components
Features:
  • Live queries
  • Optimistic updates
  • Automatic reconnection

HTTP Client

Best for:
  • Server-side rendering
  • Mobile apps
  • External integrations
  • Non-React frontends
Features:
  • REST-like APIs
  • Standard HTTP semantics
  • OpenAPI documentation

Next Steps

React Client

Use React hooks for real-time updates

HTTP API

Learn more about defining HTTP endpoints

Authentication

Secure your HTTP endpoints

Testing

Test your HTTP endpoints