# Migrate from @hulla/api 1.x

> Migrate a 1.x procedure to a working 2.0 API route.

Move one route at a time from the 1.x procedure API to a 2.0 contract, server handler, and client call. This example starts with the stable 1.2 API and ends with a working request.

If your app already uses `defineContract()`, `defineServer()`, and `createClient()`, it uses the current model.

## See the shape of the change

In 1.x, the procedure carried its output and implementation together. In 2.x, the shared contract stops before server behavior begins.

Change in `src/api/health.ts`

The HTTP declaration can now be imported without pulling server behavior into the client.

```diff filename="src/api/health.ts"
-import { api } from '@hulla/api'
-import { z } from 'zod'
-
-const a = api()
-export const health = a
-  .procedure('health', 'get')
-  .output(z.object({ ok: z.literal(true) }))
-  .define(() => ({ ok: true }))
+import { defineContract, response, route } from '@hulla/api'
+
+export const contract = defineContract({
+  routes: {
+    health: route.get('/health', {
+      responses: { 200: response.json<{ ok: true }>() },
+    }),
+  },
+})
```

The contract is intentionally incomplete on its own. That is what makes it safe to share.

## 1. Implement the contract on the server

Create a server-only module and cover every selected route:

```ts filename="src/api/implementation.ts"
import { defineServer } from '@hulla/api/server'
import { contract } from './contract'

export const implementation = defineServer(contract).implement({
  health: ({ response }) => response(200, { ok: true }),
})
```

Direct handler returns and implicit success statuses are gone. `response(200, body)` connects the runtime value to the exact contract branch it fulfills.

## 2. Mount the implementation

The portable implementation does not start a server. Mount it in the host that already owns routing and deployment.

```ts filename="src/api/handler.ts"
import { fetchAdapter } from '@hulla/api/fetch'
import { implementation } from './implementation'

export const handler = fetchAdapter().mount(implementation)
```

`handler` is a `(Request) => Promise<Response>` function. Use a native adapter instead when Express, Fastify, Hono, or another framework should keep its own router and context.

## 3. Construct the client directly

The v2 client starts from a contract node and an explicit transport. There is no generated runtime and no terminal builder step.

```ts filename="src/api/client.ts"
import { createClient } from '@hulla/api/client'
import { fetchTransport } from '@hulla/api/fetch'
import { contract } from './contract'

const health = createClient(contract.routes.health, {
  transport: fetchTransport({ baseUrl: 'https://api.example.com' }),
})

const result = await health()

if (result.status === 200) {
  console.log(result.body.ok) // true
}
```

Build a client from the root contract for the whole call tree, or from a router/route selection when a module needs only part of it.

## Update the old habits

- **Positional input becomes named HTTP input.** Pass `{ params, query, headers, body }` so the call mirrors the declared boundary.
- **Expected failures become status values.** Declare a `404` or `409`, then narrow `result.status` on the client.
- **Client middleware moves to construction.** Pass `middleware: [...]` to `createClient()`.
- **Framework state stays at the adapter.** Expose only the request context handlers actually need.
- **Local calls use a transport.** Reach the same implementation with `inProcessTransport()` when caller and server share a process.

## Revisit runtime behavior, not just types

The compiler will catch missing routes and wrong response shapes. It cannot decide your boundary policy.

Before finishing the migration, review:

- whether each JSON declaration should be type-only, schema-validated, or a codec;
- which non-`2xx` outcomes are expected contract responses;
- request-body limits and preservation required by the host;
- repeated response headers, stream ownership, and cancellation;
- browser/server module boundaries for clients and implementations.

The [migration reference](https://hulla.dev/docs/api/reference/migration.md) is the old-to-new lookup for package moves and removed 1.x behavior. For the new model in context, continue with [API contracts](https://hulla.dev/docs/api/core/contracts.md), [Implementing the server](https://hulla.dev/docs/api/core/servers.md), and [Calling the API](https://hulla.dev/docs/api/core/clients.md).
