Skip to main content

Developer Workflow

This guide covers day-to-day development tasks for the Workloom API and web app.

Running the Full Stack​

# Install dependencies
pnpm install

# Start the API server (port 4050)
pnpm api:start

# Start the web dev server (port 3050, proxies to API)
pnpm web:start

# Run all tests
pnpm test

# Type-check the entire monorepo
pnpm typecheck

Adding a New API Endpoint​

Step 1: Define the Route​

Create or update a route file in packages/registry-server/src/routes/:

import { createRoute, z } from '@hono/zod-openapi'

const myRoute = createRoute({
method: 'get',
path: '/v1/my-resource',
request: {
query: z.object({ limit: z.number().optional() }),
},
responses: {
200: {
content: { 'application/json': { schema: MyResponseSchema } },
description: 'Success',
},
},
})

Step 2: Implement the Handler​

Register the route on the Hono app in packages/registry-server/src/index.ts:

app.openapi(myRoute, async (c) => {
const { limit } = c.req.valid('query')
const result = await service.getMyResource(limit)
return c.json(result, 200)
})

Step 3: Regenerate the OpenAPI Spec​

# Start the API server and extract the spec
pnpm api:start &
curl http://localhost:4050/v1/openapi.json > apps/api/openapi.json
kill %1

Step 4: Regenerate the Client​

pnpm --filter @workloom/workloom-api generate

This produces new typed client functions, React Query hooks, TypeScript types, and Zod schemas in packages/workloom-api/src/generated/.

Step 5: Verify​

# Type-check generated output
pnpm --filter @workloom/workloom-api generate:check

# Run full type-check
pnpm typecheck

# Run tests
pnpm test

Generation Pipeline Commands​

CommandPurpose
pnpm --filter @workloom/workloom-api generateRun kubb to regenerate client from spec
pnpm --filter @workloom/workloom-api generate:checkGenerate + tsc --noEmit validation
pnpm typecheckType-check entire monorepo

Kubb Configuration​

The generation pipeline is configured in packages/workloom-api/kubb.config.ts:

PluginOutputPurpose
pluginTs./src/generated/types/TypeScript interfaces
pluginClient./src/generated/client/Typed fetch functions
pluginReactQuery./src/generated/hooks/React Query hooks
pluginZod./src/generated/zod/Zod validation schemas

Import paths:

  • Client functions use @workloom/fetch-client for transport.
  • Hooks import from @workloom/react-query.

Using Generated Hooks in the Web App​

After regenerating, new hooks are immediately available:

import { useMyResource } from '@workloom/workloom-api'

function MyPage() {
const { accessToken, headers } = useAccessToken()
const { data } = useMyResource({
client: { headers },
query: { enabled: !!accessToken },
})
// ...
}

CI Validation​

CI enforces two synchronization checks:

  1. Spec freshness: If registry-server routes change but openapi.json is not regenerated, CI fails.
  2. Client freshness: If openapi.json changes but kubb is not rerun, CI fails (detected by diffing generated output against committed code).

Package Dependencies​

@workloom/registry-server (Hono + zod-openapi)
produces -> apps/api/openapi.json

apps/api/openapi.json
consumed by -> @workloom/workloom-api (kubb)

@workloom/workloom-api (generated hooks/client)
depends on -> @workloom/fetch-client (transport)
depends on -> @workloom/react-query (query layer)

apps/web (React SPA)
depends on -> @workloom/workloom-api (generated hooks)
depends on -> @supabase/supabase-js (Supabase login for API-mediated OIDC)

Common Tasks​

Update an existing endpoint's response shape​

  1. Modify the Zod schema in packages/registry-server/src/routes/.
  2. Regenerate spec and client (Steps 3-5 above).
  3. Fix any type errors in apps/web from the new response shape.

Add a new composition hook​

  1. Create apps/web/src/hooks/useMyModel.ts.
  2. Import generated hooks from @workloom/workloom-api.
  3. Accept { headers, enabled } options.
  4. Pass options to each generated hook call.
  5. Use getCombinedStatus to derive combined loading state.

Debug an API request​

  1. Check GET /v1/openapi.json for the expected request/response schema.
  2. Verify the Bearer token is being sent (Network tab → Authorization header).
  3. Check useAccessToken() returns a non-null token.
  4. Verify enabled: !!accessToken is preventing premature queries.