Your first service
Trace the Orders extension's function through its public service and HTTP route.
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.
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; to extend this one, use
relkit add function --service orders.
Keep the existing Orders files while following this walkthrough.
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:
/** 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:
/** 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 });
});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:
/** 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:
/** 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 later enforces identity and ownership.
Verify the boundary
From relkit-orders, run:
Create tests/integration/orders.route.test.ts from the HTTP route test
before running its command below. These tests are Orders extensions, not files shipped by minimal.
bun run check
bun run typecheck
bun test tests/unit/orders.function.test.ts
bun test tests/integration/orders.route.test.tsExpect 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 to change pricing and verify the result.