Your first HTTP route
Read the Orders endpoint extension and verify its request and response boundary.
Follow a POST /orders request from its JSON body to the validated function result. That chapter creates this route from maintained API-candidate sources. Follow one POST /orders request from JSON to a validated
function result and verify both accepted and rejected input. Run commands from the app root.
Select the public operation
The Orders domain calculates a price in createOrder. Its service exposes that operation to
the route. Read the existing 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 service is the public boundary. A route imports the service, not a private function file. If you have the Orders functions but no route yet, scaffold it from the application root:
bun run relkit add route /orders \
--mode service-route --service orders --map POST=createOrderSkip this command when continuing from the Orders endpoint chapter,
which already created that file. The scaffold maps POST to the function and defaults to HTTP
200; edit it to match the 201 response below. The route’s folder supplies /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 },
});member: "createOrder" selects the public operation. successStatus: 201 changes only the
successful response status; RELKIT infers the JSON input and output contracts from the function.
The endpoint chapter changes the price rule without editing this route.
Send a request and inspect the result
Start the app with bun run dev. In another terminal, send a valid request:
curl -i -X POST http://localhost:3000/orders \
-H 'content-type: application/json' \
-d '{"orderId":"route-1","sku":"book","quantity":3}'Expect HTTP 201 and totalCents: 300. The starter’s HTTP test checks the same status and
body in process. Create tests/integration/orders.route.test.ts from this source:
import { afterAll, expect, test } from "bun:test";
import { createTestApplication } from "@relkit/testing";
import config from "../../relkit.config.js";
const testApp = await createTestApplication(config, { resourceProviders: "fake" });
test("POST /orders", async () => {
const response = await testApp.http.request("/orders", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ orderId: "order-1", sku: "book", quantity: 3 }),
});
expect(response.status).toBe(201);
expect(await response.json()).toEqual({
orderId: "order-1",
sku: "book",
totalCents: 300,
});
});
afterAll(() => testApp.close());Run bun test tests/integration/orders.route.test.ts. Then send quantity: 0; the running
server returns HTTP 422 before the handler creates an order. The test harness also maps
function-input validation to 422, but it does not reproduce every middleware or transport
behavior. Check security policy and alternate transports against the running server.
Inspect the generated contract
Run bun run check and bun run typecheck, then open /_relkit/v1/openapi.json on the
running backend. Find POST /orders, its request fields, success response, and validation
response. Edit source to change the contract; do not edit .relkit/generated output.
Select the optional Scalar UI when you want an interactive reference.
For path parameters, headers, and cookies, continue with Request mapping.