# Build a task API

> Create a task and call your API from TypeScript.

## Useful when

- You want to try the API package in a small project.
- You are creating your first route with @hulla/api.

## What you'll build

A validated task API with a shared contract, a server handler, and a typed client call.

### Before you start

- Bun and an empty directory for a TypeScript project.
- [Learn what a contract, server, and client each do.](https://hulla.dev/docs/api/mental-model.md)

Build a small API that creates a task from a title. You'll write the handler and call it from a TypeScript script, then see the new task printed in your terminal.

Everything runs in that script for now, so there's no HTTP server to set up.

## Prepare the project

Start in an empty directory. Create a package and install the API with Zod for input validation:

### Install with bun

```sh
bun init -y
```

### Install with npm

```sh
npm init -y
```

### Install with pnpm

```sh
pnpm init
```

### Install with yarn

```sh
yarn init -y
```

### Install with bun

```sh
bun add @hulla/api@2.0.0 zod
```

### Install with npm

```sh
npm install @hulla/api@2.0.0 zod
```

### Install with pnpm

```sh
pnpm add @hulla/api@2.0.0 zod
```

### Install with yarn

```sh
yarn add @hulla/api@2.0.0 zod
```

Create `src/api/` for the three files below. This guide uses Bun to run TypeScript; you can use any of the package managers above to install dependencies.

**Describe the route** — Say what a task looks like and how to create one.

First, describe the route in a [contract](https://hulla.dev/docs/api/core/contracts.md). Both the client and server will import this file so they agree on what to send and what to return.

```ts filename="src/api/contract.ts"
import { defineContract, request, response, route } from '@hulla/api'
import { z } from 'zod'

const newTask = z.object({
  title: z.string().min(1),
})

export const task = z.object({
  id: z.string(),
  title: z.string(),
  completed: z.boolean(),
})

export const contract = defineContract({
  basePath: '/api',
  routes: {
    createTask: route.post('/tasks', {
      body: request.json(newTask),
      responses: {
        201: response.json(task),
      },
    }),
  },
})
```

`z.string().min(1)` rejects an empty title. The name `createTask` will also be the name of the client method, so keep it handy for step 3.

**Add the server behavior** — Give the route a handler that creates a task.

Now write the handler. It receives a validated `body`, gives the task an ID, and returns it with status `201` (Created).

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

export const implementation = defineServer(contract).implement({
  createTask: ({ body, response }) =>
    response(201, {
      id: crypto.randomUUID(),
      title: body.title,
      completed: false,
    }),
})
```

There is one route to implement, so we need one handler. If you add another route to the contract, TypeScript will ask you to implement that one too.

**Call the API** — Send a title and print the task you get back.

Create a client from the contract and connect it to the implementation with `inProcessTransport()`. This lets the script call your handler directly, so there is no web server to start.

```ts filename="src/api/example.ts"
import { createClient } from '@hulla/api/client'
import { inProcessTransport } from '@hulla/api/in-process'
import { contract } from './contract'
import { implementation } from './implementation'

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

const result = await api.createTask({
  body: { title: 'Write the quick start' },
})

console.log(result.body)
```

Run it from the project root:

```sh
bun src/api/example.ts
```

The call prints a task shaped like this:

```ts
{
  id: 'generated-id',
  title: 'Write the quick start',
  completed: false,
}
```

Change the title and run it again. The response should contain your new text and a generated ID.

> **Note: This example does not save tasks**
>
> The handler returns a new task on each call. To keep tasks after the script exits, add a database or write them to a file.

Try an empty title next. Validation should reject the call before your handler creates a task.

## Use it in an app

You now have a working API call. Where you take it next depends on your app:

- Use [HTTP calls with Fetch](https://hulla.dev/docs/api/transports/fetch.md) to call the API from a browser or another service.
- Choose a [server adapter](https://hulla.dev/docs/api/servers.md) to serve the API with Express, Fastify, Hono, or another framework.
- Read [Organizing server code](https://hulla.dev/docs/api/core/server-composition.md) when the contract grows beyond one module.
- Read [Calling the API](https://hulla.dev/docs/api/core/clients.md) to handle responses and share client setup.
