# Tutorial

Build a coffee-ordering screen in five steps: a menu from your server, an order kept on the phone, sent back, and remembered for next time.

Each step is the whole file, with what changed highlighted.

Start from the project the [Quick start](/docs) creates, with the app running,
and clear the welcome screen:

```sh
npm run reset-project
```

## 1. Show the menu

The menu is data your server reads. Here it's a file; yours could come from a
database or an API. Create `server/menu.ts`:

```ts title="server/menu.ts"
// The menu. Yours could come from a database or an API: anything your server
// can read.
export type Coffee = { id: string; name: string; price: number };

export async function getMenu(): Promise<Coffee[]> {
  return [
    { id: "flat-white", name: "Flat white", price: 4.5 },
    { id: "cortado", name: "Cortado", price: 4 },
    { id: "cold-brew", name: "Cold brew", price: 5 },
  ];
}
```

Then replace `server/Home.tsx`:

```tsx title="server/Home.tsx"
import { cs } from "@backtickjs/core";
import {
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
} from "@backtickjs/react-native";
import { getMenu } from "./menu.js";

export async function Home() {
  const menu = await getMenu();

  return cs`{
    const styles = $StyleSheet.create({
      screen: {
        padding: 24,
        paddingTop: ($StatusBar.currentHeight ?? 0) + 24,
        gap: 16,
      },
      title: { fontSize: 32, fontWeight: "bold" },
      row: { flexDirection: "row", justifyContent: "space-between" },
      name: { fontSize: 18 },
      price: { fontSize: 18, color: "#666" },
    });

    return (
      <$ScrollView
        contentInsetAdjustmentBehavior="automatic"
        contentContainerStyle={styles.screen}
      >
        <$Text style={styles.title}>Menu</$Text>
        {$menu.map((coffee) => (
          <$View key={coffee.id} style={styles.row}>
            <$Text style={styles.name}>{coffee.name}</$Text>
            <$Text style={styles.price}>
              {coffee.price.toLocaleString("en-US", {
                style: "currency",
                currency: "USD",
              })}
            </$Text>
          </$View>
        ))}
      </$ScrollView>
    );
  }`;
}
```

Save, and the menu appears.

- **`Home` awaits the menu on your server.** The phone never asks for it: it
  arrives with the screen.
- **`$menu` writes the menu into the screen as data.** In the script, it's an
  ordinary array, with `getMenu`'s types.
- **The styles are made in the script, with `$StyleSheet.create`.** They're
  code that runs on the phone, like the JSX.

## 2. Add a client component

Each coffee gets an "Add" button that counts its taps. A tap is handled on the
phone, so the button is a client component: a script that takes props, with
state of its own.

```tsx title="server/Home.tsx" added="2,4,13-24,56"
import { cs } from "@backtickjs/core";
import { useState } from "@backtickjs/react";
import {
  Pressable,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
} from "@backtickjs/react-native";
import { getMenu } from "./menu.js";

// A client component: it runs on the phone, with state of its own.
const AddButton = cs`() => {
  const [count, setCount] = $useState(0);
  return (
    <$Pressable onPress={() => setCount(count + 1)}>
      <$Text style={{ fontSize: 18, color: "#0a7ea4" }}>
        {count === 0 ? "Add" : "Added " + count}
      </$Text>
    </$Pressable>
  );
}`;

export async function Home() {
  const menu = await getMenu();

  return cs`{
    const styles = $StyleSheet.create({
      screen: {
        padding: 24,
        paddingTop: ($StatusBar.currentHeight ?? 0) + 24,
        gap: 16,
      },
      title: { fontSize: 32, fontWeight: "bold" },
      row: { flexDirection: "row", justifyContent: "space-between" },
      name: { fontSize: 18 },
      price: { fontSize: 18, color: "#666" },
    });

    return (
      <$ScrollView
        contentInsetAdjustmentBehavior="automatic"
        contentContainerStyle={styles.screen}
      >
        <$Text style={styles.title}>Menu</$Text>
        {$menu.map((coffee) => (
          <$View key={coffee.id} style={styles.row}>
            <$Text style={styles.name}>{coffee.name}</$Text>
            <$Text style={styles.price}>
              {coffee.price.toLocaleString("en-US", {
                style: "currency",
                currency: "USD",
              })}
            </$Text>
            <$AddButton />
          </$View>
        ))}
      </$ScrollView>
    );
  }`;
}
```

Tap "Add" a few times. Each button keeps its own count.

- **`AddButton` is `` cs`() => …` ``,** a function that runs on the phone.
  It's used as a tag, `<$AddButton />`.
- **`$useState` is React's `useState`,** from `@backtickjs/react`. Hooks work
  in client components as they do in any React component.

## 3. Share the order across the screen

Counts kept in each button can't add up to a total. The order belongs one level
up, in a client component that holds every count and draws the rows.

```tsx title="server/Home.tsx" added="11,13-43,46-58,60-77,83-91"
import { cs } from "@backtickjs/core";
import { useState } from "@backtickjs/react";
import {
  Pressable,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
} from "@backtickjs/react-native";
import { type Coffee, getMenu } from "./menu.js";

// The screen's styles: a script too, one the components below share.
const styles = cs`$StyleSheet.create({
  screen: {
    padding: 24,
    paddingTop: ($StatusBar.currentHeight ?? 0) + 24,
    gap: 16,
  },
  title: { fontSize: 32, fontWeight: "bold" },
  row: { flexDirection: "row", justifyContent: "space-between" },
  name: { fontSize: 18 },
  add: { fontSize: 18, color: "#0a7ea4" },
  total: { fontSize: 18, fontWeight: "600", marginTop: 8 },
})`;

// A client function, which any script can call.
const formatPrice = cs`(price: number) =>
  price.toLocaleString("en-US", { style: "currency", currency: "USD" })`;

// One coffee, its price until it's in the order, then how many are.
const CoffeeRow = cs`(props: {
  coffee: Coffee;
  count: number;
  onAdd: () => void;
}) => (
  <$View style={$styles.row}>
    <$Text style={$styles.name}>{props.coffee.name}</$Text>
    <$Pressable onPress={props.onAdd}>
      <$Text style={$styles.add}>
        {props.count === 0
          ? $formatPrice(props.coffee.price)
          : props.count + " ×"}
      </$Text>
    </$Pressable>
  </$View>
)`;

// The order: how many of each coffee, kept on the phone.
const Order = cs`(props: { menu: Coffee[] }) => {
  const [counts, setCounts] = $useState<Record<string, number>>({});
  const add = (id: string) =>
    setCounts({ ...counts, [id]: (counts[id] ?? 0) + 1 });

  const items = Object.values(counts).reduce((sum, n) => sum + n, 0);
  const total = props.menu.reduce(
    (sum, coffee) => sum + (counts[coffee.id] ?? 0) * coffee.price,
    0,
  );

  return (
    <$View>
      {props.menu.map((coffee) => (
        <$CoffeeRow
          key={coffee.id}
          coffee={coffee}
          count={counts[coffee.id] ?? 0}
          onAdd={() => add(coffee.id)}
        />
      ))}
      <$Text style={$styles.total}>
        {items === 0
          ? "Tap a price to add it."
          : items + " in your order · " + $formatPrice(total)}
      </$Text>
    </$View>
  );
}`;

export async function Home() {
  const menu = await getMenu();

  return cs`(
    <$ScrollView
      contentInsetAdjustmentBehavior="automatic"
      contentContainerStyle={$styles.screen}
    >
      <$Text style={$styles.title}>Menu</$Text>
      <$Order menu={$menu} />
    </$ScrollView>
  )`;
}
```

Tap a price to add a coffee; the total updates below.

- **`styles` and `formatPrice` are scripts too,** a value and a function rather
  than components. Each component splices the ones it uses, and the bundle
  carries each script once.
- **`Order` holds the state; `CoffeeRow` gets it as props,** a count and an
  `onAdd` callback, as in any React app. Inside client code, props can be
  anything: functions included.
- **`Home` only passes the menu:** `<$Order menu={$menu} />`. It no longer
  draws the rows itself.

## 4. Send the order to your server

The order is placed with a `fetch` from the phone. First, the server needs
somewhere to keep orders. Create `server/orders.ts`:

```ts title="server/orders.ts"
// Orders, kept in memory while the server runs. Yours would go to a database.
export type Counts = Record<string, number>;

let last: Counts | null = null;

export async function saveOrder(counts: Counts): Promise<void> {
  last = counts;
}

export async function lastOrder(): Promise<Counts | null> {
  return last;
}
```

Then add a route for them to `server/index.tsx`, and pass `Home` the address
the app reached your server at:

```tsx title="server/index.tsx" added="3,6,32-36,38-39,41-44"
import { createServer } from "node:http";
import { createRequire } from "node:module";
import { text } from "node:stream/consumers";
import { bundler } from "@backtickjs/bundler";
import { Home } from "./Home.js";
import { saveOrder } from "./orders.js";

// Development is only what `npm start` runs, which sets NODE_ENV: any other
// run, a deploy included, is production. In development, this run of the
// server: `--watch` starts a new one whenever you save, and the app, seeing a
// new one at `/live`, draws its screen again.
const development = process.env.NODE_ENV === "development";
const run = crypto.randomUUID();

// The versions of React and React Native the app is built with: this
// project's own, as the app and this server share a package.json. A server
// for apps of many versions would tell them apart, by their user agent for
// one, and bundle each for its own.
const require = createRequire(import.meta.url);
const packageVersions = {
  react: require("react/package.json").version,
  "react-native": require("react-native/package.json").version,
};

const server = createServer(async (request, response) => {
  // `npm run web` serves the app from another origin.
  response.setHeader("access-control-allow-origin", "*");
  if (development && request.url === "/live") {
    response.end(run);
    return;
  }
  if (request.method === "POST" && request.url === "/orders") {
    await saveOrder(JSON.parse(await text(request)));
    response.end();
    return;
  }
  if (request.url === "/home") {
    // Where the app reached this server, for the screen to send its order to.
    const origin = `http://${request.headers.host}`;
    try {
      const bundle = await bundler.build({
        input: <Home origin={origin} />,
        packageVersions,
      });
      const { code } = bundle.generate({ format: "cjs" });
      response.setHeader("content-type", "text/javascript");
      response.end(code);
    } catch (error) {
      // Answered rather than crashed on, so the app can show why.
      console.error(error);
      response.statusCode = 500;
      response.end(String(error));
    }
    return;
  }
  response.statusCode = 404;
  response.end();
});

server.listen(3000, () => {
  console.log("Backtick server on http://localhost:3000");
});
```

Now the screen can send its order there:

```tsx title="server/Home.tsx" added="25-32,57-58,60,63-69,92-98,103,105,113"
import { cs } from "@backtickjs/core";
import { useState } from "@backtickjs/react";
import {
  Pressable,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
} from "@backtickjs/react-native";
import { type Coffee, getMenu } from "./menu.js";

// The screen's styles: a script too, one the components below share.
const styles = cs`$StyleSheet.create({
  screen: {
    padding: 24,
    paddingTop: ($StatusBar.currentHeight ?? 0) + 24,
    gap: 16,
  },
  title: { fontSize: 32, fontWeight: "bold" },
  row: { flexDirection: "row", justifyContent: "space-between" },
  name: { fontSize: 18 },
  add: { fontSize: 18, color: "#0a7ea4" },
  total: { fontSize: 18, fontWeight: "600", marginTop: 8 },
  button: {
    marginTop: 16,
    padding: 14,
    borderRadius: 12,
    alignItems: "center",
    backgroundColor: "#0a7ea4",
  },
  buttonText: { fontSize: 18, fontWeight: "600", color: "#fff" },
})`;

// A client function, which any script can call.
const formatPrice = cs`(price: number) =>
  price.toLocaleString("en-US", { style: "currency", currency: "USD" })`;

// One coffee, its price until it's in the order, then how many are.
const CoffeeRow = cs`(props: {
  coffee: Coffee;
  count: number;
  onAdd: () => void;
}) => (
  <$View style={$styles.row}>
    <$Text style={$styles.name}>{props.coffee.name}</$Text>
    <$Pressable onPress={props.onAdd}>
      <$Text style={$styles.add}>
        {props.count === 0
          ? $formatPrice(props.coffee.price)
          : props.count + " ×"}
      </$Text>
    </$Pressable>
  </$View>
)`;

// The order: how many of each coffee, kept on the phone until it's placed.
const Order = cs`(props: { menu: Coffee[]; ordersUrl: string }) => {
  const [counts, setCounts] = $useState<Record<string, number>>({});
  const [placed, setPlaced] = $useState(false);
  const add = (id: string) =>
    setCounts({ ...counts, [id]: (counts[id] ?? 0) + 1 });
  const place = async () => {
    await fetch(props.ordersUrl, {
      method: "POST",
      body: JSON.stringify(counts),
    });
    setPlaced(true);
  };

  const items = Object.values(counts).reduce((sum, n) => sum + n, 0);
  const total = props.menu.reduce(
    (sum, coffee) => sum + (counts[coffee.id] ?? 0) * coffee.price,
    0,
  );

  return (
    <$View>
      {props.menu.map((coffee) => (
        <$CoffeeRow
          key={coffee.id}
          coffee={coffee}
          count={counts[coffee.id] ?? 0}
          onAdd={() => add(coffee.id)}
        />
      ))}
      <$Text style={$styles.total}>
        {items === 0
          ? "Tap a price to add it."
          : items + " in your order · " + $formatPrice(total)}
      </$Text>
      {items > 0 && (
        <$Pressable style={$styles.button} onPress={place} disabled={placed}>
          <$Text style={$styles.buttonText}>
            {placed ? "Ordered ✓" : "Place order"}
          </$Text>
        </$Pressable>
      )}
    </$View>
  );
}`;

export async function Home({ origin }: { origin: string }) {
  const menu = await getMenu();
  const ordersUrl = origin + "/orders";

  return cs`(
    <$ScrollView
      contentInsetAdjustmentBehavior="automatic"
      contentContainerStyle={$styles.screen}
    >
      <$Text style={$styles.title}>Menu</$Text>
      <$Order menu={$menu} ordersUrl={$ordersUrl} />
    </$ScrollView>
  )`;
}
```

Add a coffee, then tap "Place order".

- **`Home` takes props now.** The server reads them from the request, here the
  `origin`, and `Home` turns them into the screen.
- **`ordersUrl` crosses as a prop of `Order`.** On the phone, `place` posts
  the counts to it, then shows "Ordered ✓".
- **The `/orders` route is an ordinary handler:** the screen talks to your
  server like any app would.

## 5. Make it personal

The server knows your last order, so the screen can start with it.

```tsx title="server/Home.tsx" added="12,22,60-66,110-119,123,131-136"
import { cs } from "@backtickjs/core";
import { useState } from "@backtickjs/react";
import {
  Pressable,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
} from "@backtickjs/react-native";
import { type Coffee, getMenu } from "./menu.js";
import { type Counts, lastOrder } from "./orders.js";

// The screen's styles: a script too, one the components below share.
const styles = cs`$StyleSheet.create({
  screen: {
    padding: 24,
    paddingTop: ($StatusBar.currentHeight ?? 0) + 24,
    gap: 16,
  },
  title: { fontSize: 32, fontWeight: "bold" },
  usual: { fontSize: 16, color: "#666" },
  row: { flexDirection: "row", justifyContent: "space-between" },
  name: { fontSize: 18 },
  add: { fontSize: 18, color: "#0a7ea4" },
  total: { fontSize: 18, fontWeight: "600", marginTop: 8 },
  button: {
    marginTop: 16,
    padding: 14,
    borderRadius: 12,
    alignItems: "center",
    backgroundColor: "#0a7ea4",
  },
  buttonText: { fontSize: 18, fontWeight: "600", color: "#fff" },
})`;

// A client function, which any script can call.
const formatPrice = cs`(price: number) =>
  price.toLocaleString("en-US", { style: "currency", currency: "USD" })`;

// One coffee, its price until it's in the order, then how many are.
const CoffeeRow = cs`(props: {
  coffee: Coffee;
  count: number;
  onAdd: () => void;
}) => (
  <$View style={$styles.row}>
    <$Text style={$styles.name}>{props.coffee.name}</$Text>
    <$Pressable onPress={props.onAdd}>
      <$Text style={$styles.add}>
        {props.count === 0
          ? $formatPrice(props.coffee.price)
          : props.count + " ×"}
      </$Text>
    </$Pressable>
  </$View>
)`;

// The order: how many of each coffee, kept on the phone until it's placed.
// It starts as the order the server gives it.
const Order = cs`(props: {
  menu: Coffee[];
  ordersUrl: string;
  initialCounts: Counts;
}) => {
  const [counts, setCounts] = $useState(props.initialCounts);
  const [placed, setPlaced] = $useState(false);
  const add = (id: string) =>
    setCounts({ ...counts, [id]: (counts[id] ?? 0) + 1 });
  const place = async () => {
    await fetch(props.ordersUrl, {
      method: "POST",
      body: JSON.stringify(counts),
    });
    setPlaced(true);
  };

  const items = Object.values(counts).reduce((sum, n) => sum + n, 0);
  const total = props.menu.reduce(
    (sum, coffee) => sum + (counts[coffee.id] ?? 0) * coffee.price,
    0,
  );

  return (
    <$View>
      {props.menu.map((coffee) => (
        <$CoffeeRow
          key={coffee.id}
          coffee={coffee}
          count={counts[coffee.id] ?? 0}
          onAdd={() => add(coffee.id)}
        />
      ))}
      <$Text style={$styles.total}>
        {items === 0
          ? "Tap a price to add it."
          : items + " in your order · " + $formatPrice(total)}
      </$Text>
      {items > 0 && (
        <$Pressable style={$styles.button} onPress={place} disabled={placed}>
          <$Text style={$styles.buttonText}>
            {placed ? "Ordered ✓" : "Place order"}
          </$Text>
        </$Pressable>
      )}
    </$View>
  );
}`;

// A server component, drawn inside the script below: your last order, in
// words.
async function Usual({ counts, menu }: { counts: Counts; menu: Coffee[] }) {
  const summary = menu
    .filter((coffee) => counts[coffee.id])
    .map((coffee) => `${counts[coffee.id]} × ${coffee.name}`)
    .join(", ");
  return cs`<$Text style={$styles.usual}>Your usual: {$summary}</$Text>`;
}

export async function Home({ origin }: { origin: string }) {
  const menu = await getMenu();
  const ordersUrl = origin + "/orders";
  const usual = await lastOrder();

  return cs`(
    <$ScrollView
      contentInsetAdjustmentBehavior="automatic"
      contentContainerStyle={$styles.screen}
    >
      <$Text style={$styles.title}>Menu</$Text>
      {${usual === null ? null : <Usual counts={usual} menu={menu} />}}
      <$Order
        menu={$menu}
        ordersUrl={$ordersUrl}
        initialCounts={${usual ?? {}}}
      />
    </$ScrollView>
  )`;
}
```

Place an order, then reload the app: press `r` in the terminal running it.
"Your usual" appears, and the order starts with it.

- **`Usual` is a server component drawn inside a script,** with a braced
  splice: `{${<Usual counts={usual} menu={menu} />}}`. It runs on your server,
  and the phone gets only what it returns.
- **`${usual ?? {}}` is a splice of an expression,** for when a name isn't
  enough. It's the order's starting state.
- **Every user can get their own screen.** The server builds it per request,
  so `lastOrder` could read the user's account, A/B group or location.

## What you learned

| You wrote                                    | It's a                                  | It runs on           |
| -------------------------------------------- | --------------------------------------- | -------------------- |
| `async function Home()`                      | Server component                        | Your server          |
| `` cs`…` ``                                  | Client script                           | The phone            |
| `$menu`, `${usual ?? {}}`                    | Splice: a server value, written as data | Crosses to the phone |
| `` cs`(props) => …` ``, used as `<$Order />` | Client component                        | The phone            |
| `{${<Usual />}}`                             | A server component inside a script      | Your server          |

Everything you changed lives in `server/`. Deploy your server, and every user
has the new screen the next time they open it, without an app release.

Next, **[Thinking in Backtick](/docs/thinking-in-backtick)** explains the
model behind these steps.
