# Add typed RPC to a Pear app





If your host and worker are exchanging plain strings or a hand-rolled `{ type: '...' }` object over [`Bare.IPC`](/pear/reference/pear/runtime#running-workers), this guide replaces that with [HRPC](/p2p/reference/helpers/hrpc): a schema defines each method once, and both sides get generated methods to call instead of parsing messages by hand. See [Structured RPC over IPC](/bare/explanation/rpc-and-schemas#structured-rpc-over-ipc) for why this is the recommended default once you have more than a message or two.

The worked example below adds one request/response method (`getStatus`, host asks the worker for its peer count) and one send-only event (`log`, worker to host, no reply expected)—between them, the two shapes cover most of what a Pear app's worker needs to expose.

<Steps>
  <Step>
    ### Install [#install]

    ```sh skip="install-instruction"
    npm i hrpc hyperschema
    ```
  </Step>

  <Step>
    ### Define a schema [#define-a-schema]

    Register the request/response shapes on a [hyperschema](/p2p/reference/helpers/hyperschema) namespace and write it to disk. Schemas are append-only and versioned—add fields later, never renumber or remove one:

    ```js file=<rootDir>/examples/how-to/add-typed-rpc-to-a-pear-app/build-spec.js#L1-L21 title="build-spec.js" skip="example-import"
    const Hyperschema = require('hyperschema')

    const schema = Hyperschema.from('./spec/hyperschema')
    const schemaNs = schema.namespace('worker')

    schemaNs.register({
      name: 'status-response',
      fields: [
        { name: 'peers', type: 'uint' },
        { name: 'uptimeSeconds', type: 'uint' }
      ]
    })

    schemaNs.register({
      name: 'log-event',
      fields: [
        { name: 'message', type: 'string' }
      ]
    })

    Hyperschema.toDisk(schema)
    ```

    `getStatus` carries no meaningful argument, so it needs no custom request *shape*—only `status-response`. It still needs *a* request type when the method is registered below: every HRPC method has a request, and a primitive stands in when there is nothing to send. The `log` event needs a request type (`log-event`) and no response, since it never expects a reply.
  </Step>

  <Step>
    ### Register RPC methods and generate [#register-rpc-methods-and-generate]

    Point `HRPCBuilder` at the same schema directory and register each method. `getStatus` is a plain request/response call—it takes no meaningful argument, so it uses the primitive `string` as its request type rather than a registered shape. `log` sets `send: true` on its request and omits `response` entirely, marking it fire-and-forget:

    ```js file=<rootDir>/examples/how-to/add-typed-rpc-to-a-pear-app/build-spec.js#L23-L39 title="build-spec.js (continued)" skip="example-import"
    const HRPCBuilder = require('hrpc')

    const hrpc = HRPCBuilder.from('./spec/hyperschema', './spec/hrpc')
    const ns = hrpc.namespace('worker')

    ns.register({
      name: 'get-status',
      request: { name: 'string', stream: false },
      response: { name: '@worker/status-response', stream: false }
    })

    ns.register({
      name: 'log',
      request: { name: '@worker/log-event', send: true }
    })

    HRPCBuilder.toDisk(hrpc)
    ```

    Run the build script (`node build-spec.js`) to write `spec/hyperschema/` and `spec/hrpc/` to disk. Re-run it whenever the schema changes—commit the generated `spec/` directory, don't gitignore it, so both sides of the IPC stream always compile from the same generated code.
  </Step>

  <Step>
    ### Handle requests in the worker [#handle-requests-in-the-worker]

    Construct `HRPC` over `Bare.IPC` and register a handler for `getStatus`. Call `rpc.log(...)` whenever there's something to report—it returns immediately, no response awaited:

    ```js file=<rootDir>/examples/how-to/add-typed-rpc-to-a-pear-app/workers/rpc-worker.js title="workers/rpc-worker.js" skip="example-import"
    const HRPC = require('../spec/hrpc')

    const rpc = new HRPC(Bare.IPC)
    const startedAt = Date.now()

    rpc.onGetStatus(() => ({
      peers: swarm.connections.size,
      uptimeSeconds: Math.floor((Date.now() - startedAt) / 1000)
    }))

    rpc.log({ message: 'worker ready' })
    ```

    (`swarm` here is whatever [Hyperswarm](/p2p/reference/building-blocks/hyperswarm) instance the worker already holds—this guide only adds the RPC layer on top of it.)
  </Step>

  <Step>
    ### Call it from the host [#call-it-from-the-host]

    The host side constructs `HRPC` over the same `IPC` stream returned by [`pear.run`](/pear/reference/pear/runtime#running-workers), calls `getStatus()` like any async function, and listens for `log` events:

    ```js file=<rootDir>/examples/how-to/add-typed-rpc-to-a-pear-app/host.js title="host.js" skip="example-import"
    const HRPC = require('./spec/hrpc')

    const IPC = pear.run('./workers/rpc-worker.js', [pear.storage])
    const rpc = new HRPC(IPC)

    rpc.onLog(({ message }) => console.log('[worker]', message))

    const status = await rpc.getStatus('')
    console.log(`${status.peers} peer(s), up ${status.uptimeSeconds}s`)
    ```

    A generated unary method takes its request value as the first argument, so `getStatus`—registered against the primitive `string`—is called with an empty string. Method names and encodings can't drift out of sync between the two files, because both compile from the one schema in `spec/`.
  </Step>
</Steps>

## Alternatives [#alternatives]

If a schema and a build step are more than a given protocol needs, two lighter options cover the same duplex stream without codegen:

* [tiny-buffer-rpc](https://www.npmjs.com/package/tiny-buffer-rpc)
* [bare-rpc](/bare/reference/bare/modules)

## See also [#see-also]

* [HRPC reference](/p2p/reference/helpers/hrpc)—the full API, plus the duplex-streaming pattern this guide doesn't cover.
* [Hyperschema reference](/p2p/reference/helpers/hyperschema)—field options, enums, versioned types and what a schema update may change.
* [Structured RPC over IPC](/bare/explanation/rpc-and-schemas#structured-rpc-over-ipc)—when to reach for HRPC versus a lighter option.
* [Running workers in the runtime reference](/pear/reference/pear/runtime#running-workers)—`pear.run` and the `IPC` stream this rides on.
* [Type a native RPC bridge](/bare/how-to/run-on-native/type-a-native-rpc-bridge)—the `bare-rpc` primitives underneath, for a shell in a language HRPC has no generator for yet.
