---
title: Quickstart
sidebar:
  icon: rocket
description: Install tacho, define a few procedures, serve them, and call them from a typed client.
---

## Install

```package-install
npm i tacho
```

## Define a router

```ts twoslash
import { tacho } from "tacho";
import { z } from "zod";

const rpc = tacho<{ req: Request }>();

export const router = rpc({
  ping: rpc.run(() => "pong" as const),
  user: {
    get: rpc
      .input(z.object({ id: z.string() }))
      .run(({ input }) => ({ id: input.id, name: "Ada" })),
  },
});

export type Router = typeof router;

const pong = await router.ping();
await router.user.get({ id: "1" });
```

Call it with no transport: `await router.ping()` and `await router.user.get({ id: "1" })`.

## Serve it

```ts twoslash
// @noErrors
import { serve } from "srvx";
import { handle } from "tacho/transport/fetch";
import { tacho } from "tacho";

const rpc = tacho();
const router = rpc({ ping: rpc.run(() => "pong" as const) });
// ---cut---
serve({ fetch: handle(router), port: 3000 });
```

`handle()` is `(Request) => Promise<Response>`. POST only. Other methods get `405`.

## Call it over the wire

```ts twoslash
import { createClient } from "tacho/client/http";
import { tacho } from "tacho";
import { z } from "zod";

const rpc = tacho();
const router = rpc({
  ping: rpc.run(() => "pong" as const),
  user: {
    get: rpc
      .input(z.object({ id: z.string() }))
      .run(({ input }) => ({ id: input.id, name: "Ada" })),
  },
});
type Router = typeof router;
// ---cut---
const client = createClient<Router>({ url: "http://localhost:3000" });

await client.ping();
await client.user.get({ id: "1" });
```

## Common workflows

### Stream

```ts twoslash
import { createClient } from "tacho/client/http";
import { tacho } from "tacho";
import { z } from "zod";

const rpc = tacho();
const router = rpc({
  ticks: rpc.input(z.object({ n: z.number() })).run(async function* ({
    input,
  }) {
    for (let i = 0; i < input.n; i++) yield { i };
  }),
});
type Router = typeof router;
const client = createClient<Router>({ url: "http://localhost:3000" });
// ---cut---
const ticks = await client.ticks({ n: 3 });
for await (const t of ticks) console.log(t.i);
```

### Protect a procedure

```ts twoslash
import { RpcError, tacho } from "tacho";

const rpc = tacho<{ req: Request; user?: string }>();

const protect = rpc.use(async ({ ctx, next }) => {
  const user = ctx.req.headers.get("x-user");
  if (!user) {
    throw new RpcError({ code: -32001, message: "unauthorized" });
  }
  return next({ ctx: { user } });
});

export const router = rpc({
  me: protect.run(({ ctx }) => ctx.user),
});
```

### Custom path and context

```ts twoslash
import { handle } from "tacho/transport/fetch";
import { tacho } from "tacho";

const rpc = tacho();
const router = rpc({ ping: rpc.run(() => "pong" as const) });
// ---cut---
handle(router, {
  path: "/rpc",
  createContext: (req) => ({
    user: req.headers.get("x-user") ?? undefined,
  }),
});
```

## Next steps

- [Router](/tacho/router) - `tacho()`, `.input()`, `.output()`, `.run()`
- [HTTP](/tacho/transport/http) - `handle()` and `createClient`
- [Files](/tacho/files) - `File` / `Blob` over fetch
- [WebSocket](/tacho/transport/websocket) - one socket, `.ready` / `.close()`
- [SSE streaming](/tacho/sse-streaming) - `async function*` over fetch
