---
title: Your first service
description: Trace the Orders extension's function through its public service and HTTP route.
pagination: false
sidebar:
  icon: rocket
---

Trace the Orders endpoint through its route, service, and pricing function to see where each responsibility belongs. You will identify which file owns pricing, which operations the Orders service exposes, and
how a route reaches them. That chapter adds these maintained API-candidate sources to the certified
minimal starter; it does not require an uncertified creation tuple. Run commands from the app root.

> **Before you begin**
>
> Continue from the [Orders endpoint](/docs/learn/first-route), which supplies the function used
> below.

The endpoint chapter uses `relkit add function --create-service` to create the service and its
first operation together. To start a different domain, use
[`relkit add service`](/docs/modules/service/define#scaffold-a-service); to extend this one, use
[`relkit add function --service orders`](/docs/learn/concepts/functions#scaffold-a-callable-function).
Keep the existing Orders files while following this walkthrough.

1. ## Keep behavior in the Orders domain

    `src/services/orders/functions/create-order.function.ts` validates an order request and asks
    `priceOrder` to calculate the total. It does not parse an HTTP request:

    ```ts title="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 function can be called from HTTP, a job, or another function. Its input and output
    schemas apply on every invocation. Create `tests/unit/orders.function.test.ts` with this focused
    candidate test to invoke it without starting a server:

    ```ts title="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 });
    });
    ```

2. ## Expose only selected operations

    The existing `src/services/orders/service.ts` selects the functions that other parts of the app may
    call. `priceOrder` and `createOrder` remain separate operations, so changing a price rule
    does not require moving it into a route:

    ```ts title="src/services/orders/service.ts"
    /** Declares the orders service boundary and its public members. */
    import { defineService } from "@relkit/app/services";
    import createOrder from "@app/services/orders/functions/create-order.function.js";
    import priceOrder from "@app/services/orders/functions/price-order.function.js";

    export default defineService({
      functions: { createOrder, priceOrder },
    });
    ```

    The compiler discovers this service from source. You do not add it to a registration array.
    The route imports the service and selects `createOrder` for `POST /orders`:

    ```ts title="src/routes/orders/route.ts"
    /** 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 },
    });
    ```

    Exposing a service member does not itself create an endpoint or grant caller permission.
    The route provides HTTP exposure; the [auth chapter](/docs/learn/protect-orders) later enforces
    identity and ownership.

3. ## Verify the boundary

    From `relkit-orders`, run:

    Create `tests/integration/orders.route.test.ts` from the [HTTP route test](/docs/modules/http/first-route)
    before running its command below. These tests are Orders extensions, not files shipped by minimal.

    ```sh
    bun run check
    bun run typecheck
    bun test tests/unit/orders.function.test.ts
    bun test tests/integration/orders.route.test.ts
    ```

    Expect all four commands to succeed after adding the Orders sources and tests. The unit test checks the reusable
    operation; the HTTP test checks the public route's status and body. Continue with
    [the endpoint chapter](/docs/learn/first-route) to change pricing and verify the result.

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

**Reference and source**

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

- [`templates/default/v1/minimal/src/services/hello/service.ts`](https://github.com/rel-kit/relkit/blob/main/templates/default/v1/minimal/src/services/hello/service.ts)
- [`templates/default/v1/minimal/tests/unit/hello.function.test.ts`](https://github.com/rel-kit/relkit/blob/main/templates/default/v1/minimal/tests/unit/hello.function.test.ts)
