---
title: Functions
description: Define reusable behavior once, invoke it through the common engine, and test it without a transport.
sidebar:
  icon: braces
---

A function contains a piece of application behavior, such as calculating a price or creating an order. It declares runtime input and output schemas,
expected errors, provider-backed dependencies, optional publication access, and one handler. Routes, jobs,
event consumers, tools, agents, and direct calls all enter the same invocation engine.

Functions remain request- or event-scoped operations. When work must outlive the initiating request, define a
[task](/docs/modules/jobs/tasks) and expose it through a [task-first job](/docs/modules/jobs/quickstart); do not turn a
function target into a durable worker by adding queue fields.

## Scaffold a callable function

With an existing `orders` service, run from the application root:

```sh
bun run relkit add function send-receipt --service orders
```

This creates `src/services/orders/functions/send-receipt.function.ts` and exposes `sendReceipt` through
`src/services/orders/service.ts`. The placeholder accepts and returns a string `value`; replace its schemas
and handler with your domain behavior. Its stable ID is `orders.send-receipt`.

Pass `--internal` to omit public service exposure. Use `--create-service orders` instead of
`--service orders` only if the domain does not exist. No test files are generated.

For every scaffolding flag, see the [function command reference](/docs/api/cli#relkit-add-function-name).

## Read a complete function

```ts title="templates/default/v1/api/src/services/orders/functions/create-order.function.ts"
/** Validates order input, invokes the service-local pricing function, and returns the calculated order. */
import { defineFunction } from "@relkit/app/functions";
import { z } from "@relkit/app/schema";
import priceOrder from "@app/services/orders/functions/price-order.function.js";

const orderInput = z.object({
  orderId: z.string().min(1),
  sku: z.string().min(1),
  quantity: z.number().int().positive(),
});

const orderOutput = z.object({
  orderId: z.string(),
  sku: z.string(),
  totalCents: z.number().int().nonnegative(),
});

const createOrder = defineFunction({
  input: orderInput,
  output: orderOutput,

  handler: async (input) => {
    const { totalCents } = await priceOrder.invoke({ quantity: input.quantity });
    const order = {
      orderId: input.orderId,
      sku: input.sku,
      totalCents,
    };

    return order;
  },
});

export default createOrder;
```

The handler receives validated input. Its return value is validated before the invocation succeeds. The example
calls `priceOrder.invoke(...)`, so the nested operation keeps validation, deadlines, cancellation, tracing, and
causation. Calling another descriptor's handler directly bypasses that contract.

### Use this function from the frontend

Expose `createOrder` through its Orders service and a route. The API example maps that public
member to `POST /orders`:

```ts title="src/routes/orders/route.ts — backend"
/** Maps the HTTP endpoint to its owning service's public member. */
import { defineServiceRoutes } from "@relkit/app/routes";
import orders from "@app/services/orders/service.js";

export const { POST } = defineServiceRoutes(orders, {
  POST: { member: "createOrder", successStatus: 201 },
});
```

After [frontend setup and pulling this API example's contract](/guides/frontend/connect-react),
render this component under `RelkitClientProvider`:

```tsx title="src/CreateOrder.tsx — frontend"
"use client";

/** Calls the API example's Orders function through its public route, retaining the generated input and price result. */
import { useRelkitClient, useRouteMutation } from "@relkit/client/react";

/** Creates one order in the API example without importing the backend service.
 * @returns A guarded submission action and its typed price or failure. */
export function CreateOrder() {
  const { status } = useRelkitClient();
  const create = useRouteMutation("POST /orders");

  return (
    <section>
      <button
        disabled={status !== "ready" || !create.isIdle}
        onClick={() => create.mutate({ orderId: crypto.randomUUID(), sku: "book", quantity: 2 })}
      >
        Create an order
      </button>
      {create.isPending && <p role="status">Creating…</p>}
      {create.isSuccess && <p>Total: {create.data.totalCents} cents</p>}
      {create.isError && (
        <p role="alert">{create.error.message}. Check the outcome before retrying.</p>
      )}
    </section>
  );
}
```

This calls the same `createOrder` function through `POST /orders`: `orderId`, `sku`, and `quantity`
come from its input schema, and `totalCents` comes from its output schema. The browser calls RPC;
it never imports the descriptor or calls `.invoke()`. Service membership needs a client-exposed
route to become a browser operation.

Reference: [`useRouteMutation`](/docs/api/client#useroutemutation).
Continue with [mutation state and recovery](/docs/modules/client/mutations).

## Define the boundary

### Start with input and output

Put a schema beside its only consumer, or in the owning domain when several descriptors share it. Keep
transport fields such as cookies and raw headers in the route unless they are genuine domain input.

### Declare failures and capabilities

List only errors callers are expected to handle. Declare only the jobs, buckets, caches, and agents the
handler uses. Event publication is separate: list exact event IDs in `publishes`.

### Implement transport-independent behavior

Use the supplied context, honor its abort signal, and return the declared output. Let unexpected defects
cross RELKIT's safe boundary rather than converting them into unstable public errors.

### Hooks and transport policy

`onBefore` transforms validated input before the handler. `onAfter` transforms successful output before final
output validation. Use hooks only for function policy that must apply to every invocation source; use HTTP
middleware for request-native policy.

## Test the operation directly

```ts title="templates/default/v1/api/tests/unit/orders.function.test.ts"
/** Checks orders.function through the supported public RELKIT APIs. */
import { expect, test } from "bun:test";
import { invokeFunction } from "@relkit/testing";
import orders from "@app/services/orders/service.js";

test("createOrder invokes pricing", async () => {
  const result = await invokeFunction(orders.createOrder, {
    orderId: "order-1",
    sku: "book",
    quantity: 3,
  });

  expect(result).toEqual({ orderId: "order-1", sku: "book", totalCents: 300 });
});
```

```sh title="Terminal — relkit-orders"
bun test tests/unit/orders.function.test.ts
```

`invokeFunction` runs the descriptor through an isolated engine. A function that needs provider clients receives
explicit test fakes; tests do not infer production replacements from `RELKIT_ENV`.

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/functions` API](/docs/api/functions)

- [`templates/default/v1/api/src/services/orders/functions/create-order.function.ts`](https://github.com/rel-kit/relkit/blob/main/templates/default/v1/api/src/services/orders/functions/create-order.function.ts)
