Intro

API Development

Generating the typed API client SDK, OpenAPI documentation, and MCP server SDK from backend Hono routes.

API Development

DevStride auto-generates a fully typed frontend API client, public OpenAPI documentation, and the MCP server's SDK from the same backend Hono route definitions. This ensures the frontend SDK, published API docs, and MCP tool surface always match the backend — no manual sync needed.

Generating the API Client

ds script generate-api-client

What It Does

  1. Scans all Hono route definitions in the backend (routes registered against the shared registry in @/libs/interface-adapters/hono)
  2. Filters out any route marked x-skip-client
  3. Generates an OpenAPI 3.1.0 specification from the remaining route metadata and writes it to .ds/tmp/openapi.json
  4. Produces a class-based TypeScript SDK in frontend/src/api/ via @hey-api/openapi-ts
  5. Formats the output with Prettier

The generated SDK provides:

  • Fully typed request and response objects
  • Method signatures matching each API endpoint
  • Compile-time safety — the frontend won't build if the SDK is out of sync with the backend

When to Run

Run ds script generate-api-client after:

  • Adding a new API endpoint
  • Changing a route path
  • Modifying request body or response types
  • Adding or removing query parameters
  • Deleting an endpoint

Excluding Routes

Add x-skip-client metadata directly on a route's config object so it's excluded from the generated frontend SDK (e.g., internal admin endpoints, webhook receivers):

// backend/src/modules/integrations/application/jira-webhook.hono.handler.ts
const route = {
  'x-skip': true,
  'x-skip-client': true,
  method: 'post',
  path: '/v1/organizations/integrations/jira/{accountId}/webhook',
  summary: 'Jira Webhook Handler',
  operationId: 'jiraWebhook',
  tags: ['Integration'],
  // ...
} satisfies RouteConfig;

Generating API Documentation

ds script generate-api-docs

What It Does

Generates a comprehensive OpenAPI 3.0.0 specification and writes it to:

backend/src/modules/api/interface-adapters/lambda/http/openapi.json

Routes marked x-skip or x-skip-client are excluded from this spec. The generated document includes a full quick-start guide baked into its info.description — API key creation, Basic Auth vs. the custom Authorization: apiKey=...&apiSecret=... header format, a first-call curl example, and a troubleshooting table for common 401/403 responses — plus the request/response schemas and tags for every included endpoint. The file is formatted with Prettier after being written so it stays diff-stable regardless of who regenerates it.

Excluding Routes from Documentation

Two exclusion levels are available, both set as metadata on the route config object:

MetadataEffect
x-skipExcluded from documentation only; still appears in the API client SDK
x-skip-clientExcluded from both documentation and the API client SDK

Use x-skip for internal endpoints that developers need to call programmatically but shouldn't be in public docs. Use x-skip-client for endpoints that the frontend never calls (webhooks, healthchecks).

Generating the MCP Server SDK

ds script generate-api-mcp

What It Does

This command orchestrates the full generation chain needed to keep the DevStride MCP server (packages/mcp/) in sync with the backend API, in order:

  1. generate-api-client — runs first and writes the canonical .ds/tmp/openapi.json (also regenerates frontend/src/api/* as a side effect)
  2. generate-api-docs — writes the public docs openapi.json
  3. packages/mcp generate — runs packages/mcp/scripts/generate.ts, which consumes that same .ds/tmp/openapi.json and rewrites the generated MCP SDK under packages/mcp/src/devstride/gen/:
    • sdk.gen.ts — typed service classes from @hey-api/sdk
    • types.gen.ts — TypeScript types
    • schemas.gen.ts — per-component JSON Schemas
    • operations.gen.ts — per-operation { path?, query?, body? } JSON Schemas
    • components.gen.ts — component schemas record used for Ajv $ref resolution

These generated files are committed to git, so ds script generate-api-mcp is the one command to run after any backend OpenAPI change that should be reflected in the MCP server's tools.

Run the generators on the host, and check the files

The generators are pure source-to-source: they need no running backend, database or AWS. Two traps still catch people.

Never run them through ds worktree cli. An instance's cli container mounts backend but not frontend or packages, so a container-side run writes the backend openapi.json, discards the client and MCP output, and exits 0 with its usual success message. The result is a committed OpenAPI file that disagrees with the client generated from it. On the host, with no stack running:

DEVSTRIDE_STAGE=dev DEVSTRIDE_REGION=us-east-1 ds -u script generate-api-client
DEVSTRIDE_STAGE=dev DEVSTRIDE_REGION=us-east-1 ds -u script generate-api-mcp

-u skips the AWS login; the wrapper still needs an existing .ds/bind/<stage>-<region>.env, which is per checkout (copy one from another checkout if this one has none). Then verify the artifacts, never the exit code — grep the generated file for the field you added and check all three paths moved:

git status --short frontend/src/api packages/mcp/src/devstride/gen backend/src/modules/api/interface-adapters/lambda/http/openapi.json

The backend OpenAPI file changing alone is the signature of a container-side run.

The pre-push hook's regen does not reach the push it runs in. The hook regenerates the API artifacts and amends the commit, but git resolved the ref it is pushing before the hook ran, so the remote receives the pre-amend commit and the amended one stays local. After any push whose hook printed "Amending commit", compare git rev-parse HEAD with git rev-parse origin/<branch> and push again with --force-with-lease before opening or re-reviewing the pull request. The repository's ds-api-regen skill is the authority on both traps.

Backend-to-Frontend Call Flow

Understanding how a frontend API call reaches the backend helps when debugging:

Frontend SDK call
    → HTTP request to API Gateway
    → Lambda handler (Hono route)
    → Input validation
    → Command/Query constructed
    → CommandBus/QueryBus.execute()
    → Service handler (business logic)
    → Repository (database access)
    → Result<T, E> returned
    → Response formatted and sent

ds g command <name> -m <module> and ds g query <name> -m <module> scaffold the command or query, its service handler and its init registration; the HTTP entry point they emit uses an older Lambda-handler pattern, so hand-write a *.hono.handler.ts from a neighbouring handler instead (see Scaffolding CQRS Code). The API client generator then ensures the frontend matches that entry point.

Next Steps