Skip to content

API Evolution

Routa keeps an accepted contract in .routa/openapi-baseline.json and compares every generated document against it. That gives you a mechanical answer to “is this change breaking?” and a deliberate step for accepting the change when it is intentional.

  • A committed .routa/openapi-baseline.json, written by routa scaffold.
  • A project that passes routa check.
  1. Check compatibility after changing a contract. Run this in CI on every pull request.

    Terminal window
    npx routa openapi breaking

    The command fails for removed operations and for inputs that became required, whether the input is a query parameter, header, cookie, path parameter, or request body.

  2. Deprecate rather than delete when an operation must go away. Declare the deprecation on the route contract.

    src/routes/legacy/users/route.ts
    import { createRoute, createRouteRoot } from "@routa-ts/core";
    import { z } from "zod";
    const route = createRouteRoot("/legacy/users");
    export default route({
    get: createRoute({
    deprecation: {
    sunset: "2027-01-01",
    replacement: "/users",
    },
    responses: {
    success: {
    status: 200,
    schema: z.object({ message: z.string() }),
    },
    },
    run: () => ({
    type: "success",
    data: { message: "Use GET /users instead." },
    }),
    }),
    });

    Routa marks the operation deprecated in the generated OpenAPI. For a replacement inside the same API, use a leading / and Routa verifies the target route exists. For another API, use an absolute http: or https: URL such as https://api.example.com/v2/users.

  3. Emit lifecycle headers if clients should learn about the deprecation at runtime.

    src/routa.ts
    import { createRouta } from "@routa-ts/core";
    export default createRouta({
    lifecycleHeaders: true,
    });

    Deprecated operations then respond with Deprecation: true, plus Sunset and a Link with rel="successor-version" when those are declared.

  4. Accept the change once it has been reviewed. This is the only command that rewrites the baseline.

    Terminal window
    npx routa openapi breaking --update-baseline

A compatible change:

OpenAPI breaking check passed. No breaking changes detected.

Accepting an intentional change:

OpenAPI baseline updated.

A deprecated operation with lifecycleHeaders enabled:

HTTP/1.1 200 OK
deprecation: true
sunset: 2027-01-01
link: </users>; rel="successor-version"
Command Fails on Use it
routa openapi check Any difference from the baseline Locally, to see that a change was noticed
routa openapi breaking Removed operations and newly required inputs In CI, as a merge gate

openapi check is strict by design: adding an optional field trips it. That makes it a good local signal and a poor merge gate, which is what openapi breaking is for.

Middleware can describe the security an operation requires without Routa implementing any of it:

src/middleware/auth.ts
import { createMiddleware } from "@routa-ts/core";
createMiddleware({
openapi: {
security: [{ bearerAuth: [] }],
permissions: ["users.read"],
},
// Application-owned credential and permission logic.
});

Routa emits these as OpenAPI security and x-routa-authz. Because they are part of the document, an operation that was public becoming authenticated is reported by routa openapi breaking like any other breaking change.

Problem Fix
Missing .routa/openapi-baseline.json. Run routa scaffold first. The project has no accepted contract. Run routa scaffold against the reviewed OpenAPI document first.
ROUTA_DEPRECATION_REPLACEMENT_ROUTE_NOT_FOUND The local replacement path is not a route in this project. Check the path, or run routa generate if metadata is stale.
ROUTA_DEPRECATION_REPLACEMENT_URL_INVALID Use /local-path for this API or an absolute http(s) URL for another.
Lifecycle headers do not appear lifecycleHeaders defaults to off. Enable it in createRouta(), and confirm the route declares deprecation.
The baseline changes on every run Generation is deterministic. A repeating diff means the source really is changing, often from stale .routa/routes.gen.ts.