# @hulla/api

> Type-check HTTP requests, handlers, and clients against one shared contract, across the hosts and transports your app uses.

## Useful when

- You want HTTP paths, inputs, statuses, and bodies to stay typed together.
- Frontend and backend work should share a contract without sharing server code.
- One API must fit several frameworks, runtimes, or process boundaries.

`@hulla/api` keeps methods, paths, inputs, and response types in one shared contract. Your server implements that contract; your client calls it through the transport you choose.

**Interactive example:&#x20;**&#x45;dit the contract, server, or caller in the code editor. A real TypeScript worker checks every change; shortcuts can load a breaking or repaired project.

The browser runs a real TypeScript compiler against a checked-in snapshot of @hulla/api declarations. It makes no network request to an API; the repair is a preset edit, not a live AI agent.

```ts
import { defineContract, route, response } from '@hulla/api'

export const contract = defineContract({
  routes: {
    profile: route.get('/profile', {
      responses: { 200: response.json<{ displayName: string }>() },
    }),
  },
})
```

[Build the working example](https://hulla.dev/guides/api/quick-start.md)

- [Build your first route](https://hulla.dev/guides/api/quick-start.md) — Contract, handler, and client in one working example.
- [Understand the model](https://hulla.dev/docs/api/mental-model.md) — See what belongs in shared, server, and client code.

## One contract, one call

```ts filename="src/api/projects.ts"
import { defineContract, request, response, route } from '@hulla/api'
import { createClient } from '@hulla/api/client'
import { inProcessTransport } from '@hulla/api/in-process'
import { defineServer } from '@hulla/api/server'

const contract = defineContract({
  routes: {
    createProject: route.post('/projects', {
      body: request.json<{ name: string }>(),
      responses: { 201: response.json<{ id: string; name: string }>() },
    }),
  },
})

const implementation = defineServer(contract).implement({
  createProject: ({ body, response }) =>
    response(201, { id: crypto.randomUUID(), name: body.name }),
})

const api = createClient(contract, {
  transport: inProcessTransport(implementation),
})

const result = await api.createProject({ body: { name: 'Documentation' } })
```

## What changes for your application?

| Concern            | How it works                                                                                                                           |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP behavior      | Methods, paths, headers, and statuses stay explicit. A declared `404` is a typed result the client can handle.                         |
| Framework choice   | Native adapters connect the implementation to your host. The host keeps routing, middleware, and deployment.                           |
| Runtime validation | Add a [Standard Schema](https://standardschema.dev/) validator where needed. Type-only declarations skip application-shape validation. |
| Contract drift     | Missing handlers, undeclared statuses, and incorrect response types become TypeScript errors.                                          |

Use codecs for values such as `Date` that need different in-memory and wire representations.

## How it compares

These tools overlap, but they start from different sources of truth:

| Tool                                         | The source of truth               | It feels most natural when                                                         |
| -------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------- |
| `@hulla/api`                                 | A standalone HTTP contract        | HTTP details matter and the same contract must cross different hosts or transports |
| [tRPC](https://trpc.io/)                     | A server procedure router         | One TypeScript application wants inference directly from its server implementation |
| [oRPC](https://orpc.dev/)                    | Procedures or a separate contract | You want its RPC/OpenAPI builders and broad procedure ecosystem                    |
| [ts-rest](https://ts-rest.com/)              | A standalone REST contract        | You want a contract-first REST client with its supported server integrations       |
| [Hono RPC](https://hono.dev/docs/guides/rpc) | A typed Hono route chain          | The server already belongs to Hono and clients should infer from that app          |

Choose `@hulla/api` when the boundary should outlive any one framework and when transport choice should remain explicit. If the API is only an internal function call, use an ordinary function. If a framework-owned typed client already matches every consumer, adding a separate contract may not buy you enough.

## Choose your next step

The core package includes contracts, clients, servers, Fetch, and in-process calls. Add integrations only for the boundary you need.

- [Installation](https://hulla.dev/docs/api/installation.md) — Install the core package and optional integrations.
- [Server adapters](https://hulla.dev/docs/api/servers.md) — Connect your existing framework or runtime.
- [Transports](https://hulla.dev/docs/api/transports.md) — Choose Fetch, in-process, MessagePort, or WebSocket.
- [Migration from 1.x](https://hulla.dev/docs/api/start/migration.md) — Move an existing application to the current API.
