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.
Functions are the core building blocks of your Confect backend. Confect supports three types of functions: queries for reading data, mutations for writing data, and actions for external integrations.
Function Types
Queries Read data from the database with real-time updates
Mutations Write data to the database transactionally
Actions Perform external operations and side effects
Creating Functions
Functions in Confect follow a two-phase pattern: specification and implementation.
Function Specification
First, define your function’s interface using FunctionSpec:
import { FunctionSpec } from "@confect/core" ;
import { Schema } from "effect" ;
export const getUserSpec = FunctionSpec . query ({
args: Schema . Struct ({
userId: Schema . String ,
}),
returns: Schema . Struct ({
id: Schema . String ,
name: Schema . String ,
email: Schema . String ,
}),
});
Function Implementation
Then, implement the function logic using FunctionImpl:
import { FunctionImpl } from "@confect/server" ;
import { Effect } from "effect" ;
import { api } from "./api" ;
export const getUser = FunctionImpl . make (
api ,
"users" ,
"get" ,
({ userId }) =>
Effect . gen ( function* () {
const db = yield * DatabaseReader < typeof databaseSchema >();
const user = yield * db
. table ( "users" )
. get ( userId );
if ( ! user ) {
return yield * Effect . fail ( new Error ( "User not found" ));
}
return {
id: user . _id ,
name: user . name ,
email: user . email ,
};
}). pipe ( Effect . orDie )
);
Query Functions
Queries are read-only functions that can access the database and run in real-time.
Available Services
DatabaseReader Read data from any table
Auth Access user authentication info
StorageReader Get URLs for stored files
QueryRunner Run other queries
Query Example
import { DatabaseReader , Auth } from "@confect/server" ;
import { Effect } from "effect" ;
const listUserPosts = FunctionImpl . make (
api ,
"posts" ,
"listByUser" ,
({ limit = 10 }) =>
Effect . gen ( function* () {
const db = yield * DatabaseReader < typeof databaseSchema >();
const auth = yield * Auth ;
const identity = yield * auth . getUserIdentity ;
const posts = yield * db
. table ( "posts" )
. index ( "by_author" , ( q ) => q . eq ( "authorId" , identity . subject ))
. take ( limit )
. collect ();
return posts ;
}). pipe ( Effect . orDie )
);
Queries are cached and automatically rerun when data changes, providing real-time updates to clients.
Mutation Functions
Mutations are transactional functions that can read and write to the database.
Available Services
DatabaseReader Read data from any table
DatabaseWriter Write data to any table
Auth Access user authentication
Scheduler Schedule future functions
StorageReader Read file storage
StorageWriter Write to file storage
MutationRunner Run other mutations
Mutation Example
import { DatabaseWriter , Auth , Scheduler } from "@confect/server" ;
import { Effect , Duration } from "effect" ;
const createPost = FunctionImpl . make (
api ,
"posts" ,
"create" ,
({ title , content }) =>
Effect . gen ( function* () {
const db = yield * DatabaseWriter < typeof databaseSchema >();
const auth = yield * Auth ;
const scheduler = yield * Scheduler ;
const identity = yield * auth . getUserIdentity ;
const postId = yield * db . table ( "posts" ). insert ({
title ,
content ,
authorId: identity . subject ,
createdAt: Date . now (),
published: false ,
});
// Schedule publish in 1 hour
yield * scheduler . runAfter (
Duration . hours ( 1 ),
refs . internal . posts . publish ,
{ postId }
);
return { id: postId };
}). pipe ( Effect . orDie )
);
Mutations are transactional - if any part fails, the entire mutation is rolled back.
Action Functions
Actions can perform external operations like API calls, send emails, or access Node.js APIs.
Convex Actions
Standard actions run in the Convex runtime:
import { ActionRunner , Auth } from "@confect/server" ;
import { Effect } from "effect" ;
const sendEmail = FunctionImpl . make (
api ,
"notifications" ,
"sendEmail" ,
({ to , subject , body }) =>
Effect . gen ( function* () {
const auth = yield * Auth ;
const runMutation = yield * MutationRunner ;
const identity = yield * auth . getUserIdentity ;
// Call external email service
const response = yield * Effect . tryPromise (() =>
fetch ( "https://api.emailservice.com/send" , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({ to , subject , body }),
})
);
// Log the email
yield * runMutation (
refs . internal . emailLog . create ,
{ to , subject , sentAt: Date . now () }
);
return { success: true };
}). pipe ( Effect . orDie )
);
Node Actions
Node actions have access to the full Node.js runtime:
import { FunctionSpec } from "@confect/core" ;
import { Schema } from "effect" ;
export const processFile = FunctionSpec . nodeAction ({
args: Schema . Struct ({
fileId: Schema . String ,
}),
returns: Schema . Struct ({
processed: Schema . Boolean ,
}),
});
import { FileSystem } from "@effect/platform-node" ;
import { Effect } from "effect" ;
const processFileImpl = FunctionImpl . make (
api ,
"files" ,
"process" ,
({ fileId }) =>
Effect . gen ( function* () {
const fs = yield * FileSystem . FileSystem ;
const storage = yield * StorageActionWriter ;
// Download file from storage
const blob = yield * storage . get ( fileId );
// Process with Node.js APIs
const buffer = Buffer . from ( await blob . arrayBuffer ());
// ... process buffer ...
return { processed: true };
}). pipe ( Effect . orDie )
);
Node actions are the only function type that can access the Node.js runtime, including the filesystem, crypto, and other Node-specific APIs.
Function Visibility
Functions can be public or internal:
Public Functions
const publicSpec = FunctionSpec . query ({
args: Schema . Struct ({ id: Schema . String }),
returns: Schema . Struct ({ data: Schema . String }),
// Public by default
});
Public functions can be called from client applications.
Internal Functions
const internalSpec = FunctionSpec . internalQuery ({
args: Schema . Struct ({ id: Schema . String }),
returns: Schema . Struct ({ data: Schema . String }),
});
Internal functions can only be called from other backend functions.
Error Handling
Use Effect’s error handling for robust function implementations:
import { Schema } from "effect" ;
class UserNotFoundError extends Schema . TaggedError < UserNotFoundError >()()
"UserNotFoundError" ,
{ userId : Schema . String }
) {
override get message () {
return `User ${ this . userId } not found` ;
}
}
const getUser = FunctionImpl . make (
api ,
"users" ,
"get" ,
({ userId }) =>
Effect . gen ( function* () {
const db = yield * DatabaseReader < typeof databaseSchema >();
const user = yield * db
. table ( "users" )
. get ( userId )
. pipe (
Effect . mapError (() => new UserNotFoundError ({ userId }))
);
return user ;
}). pipe (
Effect . catchTag ( "UserNotFoundError" , ( error ) =>
Effect . fail ( new Error ( error . message ))
)
)
);
Composing Functions
Use function runners to compose functions:
const getUserWithPosts = FunctionImpl . make (
api ,
"users" ,
"getWithPosts" ,
({ userId }) =>
Effect . gen ( function* () {
const runQuery = yield * QueryRunner ;
const user = yield * runQuery (
refs . internal . users . get ,
{ userId }
);
const posts = yield * runQuery (
refs . internal . posts . listByUser ,
{ userId }
);
return { user , posts };
}). pipe ( Effect . orDie )
);
Best Practices
Keep Functions Small
Each function should do one thing well. Compose multiple functions for complex operations.
Use Type-Safe Schemas
Define comprehensive schemas for arguments and returns to catch errors at compile time.
Handle Errors Explicitly
Use Effect’s error handling to make error cases visible in types.
Leverage Services
Use Confect services instead of raw context for better testability.
Next Steps
Database Learn about database operations
Node Actions Explore Node.js runtime actions
Scheduling Schedule functions to run later
Authentication Add user authentication