@arrirpc/server

中文文档
0.85.0发布于 2026/10/03MITTypeScriptESM + CJS近 30 天下载 4493

Typescript implementation of Arri RPC. It's built on top of H3 and uses esbuild for bundling.

原始使用文档
文档目录19 节
  1. Table of Contents
  2. Quickstart
  3. Manual Setup
  4. Install Dependencies
  5. Scaffold Your Project
  6. Usage
  7. Creating Procedures
  8. Adding Non-RPC Routes
  9. Adding Middleware
  10. Background Workers & Lifecycle Hooks
  11. Adding Client Generators
  12. Key Concepts
  13. Arri Definition File
  14. How Procedures Map To Endpoints
  15. H3 Support
  16. Arri CLI
  17. Development
  18. Building
  19. Running unit tests

Arri RPC - Typescript Server

Typescript implementation of Arri RPC. It's built on top of H3 and uses esbuild for bundling.

Parameters and responses are defined using @arrirpc/schema for automatic validation and serialization of inputs and outputs and to generate Arri Type Definitions for client generators.

Table of Contents

Quickstart

# npm
npx arri init [project-name]
cd [project-name]
npm install
npm run dev

# pnpm
pnpm dlx arri init [project-name]
cd [project-name]
pnpm install
pnpm run dev

Manual Setup

Install Dependencies

# npm
npm install -D arri
npm install @arrirpc/server @arrirpc/schema

# pnpm
pnpm install -D arri
pnpm install @arrirpc/server @arrirpc/schema

Scaffold Your Project

A basic Arri app directory looks something like this:

|-- project-dir
    |-- .arri // temp files go here
    |-- .output // final bundle goes here
    |-- src
        |-- procedures // .rpc.ts files go here
        |-- app.ts
    |-- arri.config.ts
    |-- package.json
    |-- tsconfig.json
|

Both .arri and .output should be added to your .gitignore file

.arri
.output
node_modules

Configuration File

Create an arri.config.ts in the project directory

// arri.config.ts
import { defineConfig, servers, generators } from 'arri';

export default defineConfig({
    server: servers.tsServer(),
    generators: [
        generators.typescriptClient({
            // options
        }),
        generators.dartClient({
            // options
        }),
    ],
});
App Entry

Create an app entry file in your src directory. The name of the file must match whatever you put as the entry in your arri.config.ts.

// ./src/app.ts
import { ArriApp } from 'arri';

const app = new ArriApp();

export default app;
Package.json

Setup your npm scripts:

{
    "name": "my-arri-app",
    "type": "module",
    "scripts": {
        "dev": "arri dev",
        "build": "arri build"
    },
    "dependencies": {
        ...
    },
    "devDependencies": {
        ...
    }
}

Usage

Creating Procedures

File Based Routing

Arri RPC comes with an optional file based router that will automatically register functions in the ./procedures directory that end with the .rpc.ts file extension.

|-- src
   |-- procedures
      |-- sayHello.rpc.ts // becomes sayHello()
      |-- users
          |-- getUser.rpc.ts // becomes users.getUser()
          |-- updateUser.rpc.ts // becomes users.updateUser()

Example .rpc.ts file

// ./src/users/getUser.rpc.ts
import { defineRpc } from '@arrirpc/server';
import { a } from '@arrirpc/schema';

export default defineRpc({
    params: a.object({
        userId: a.string(),
    }),
    response: a.object({
        id: a.string(),
        name: a.string(),
        createdAt: a.timestamp(),
    }),
    handler({ params }) {
        // function body
    },
});
Customizing the File Based Router
export default defineConfig({
    servers: servers.tsServer({
        procedureDir: 'procedures', // change which directory to look for procedures (This is relative to the srcDir)
        procedureGlobPatterns: ['**/*.rpc.ts'], // change the file name glob pattern for finding rpcs
    }),
    // rest of config
});

Manual Routing

For those that want to opt out of the file-based routing system you can manually register procedures like so.

// using the app instance
const app = new ArriApp()
app.rpc('sayHello',
    defineRpc({...})
);

// defining a service
const app = new ArriApp();
const usersService = defineService("users");
usersService.rpc("getUser", defineRpc({...}));
usersService.rpc("createUser", defineRpc({...}));

// register the service on the app instance
app.use(usersService);

There's also a shorthand for initializing services with procedures

// this is equivalent to what we showed above
const usersService = defineService("users", {
    getUser: defineRpc({..}),
    createUser: defineRpc({..}),
});

Creating Event Stream Procedures

Event stream procedures make use of Server Sent Events to stream events to clients.

Arri Event streams sent the following event types:

  • message - A standard message with the response data serialized as JSON
  • start - A starting message to start the stream
  • done - A message to tell clients that there will be no more events
  • heartbeat - A message periodically sent by the server to keep the connection alive.
/// message event ///
id: string | undefined;
event: 'message';
data: Response; // whatever you have specified as the response serialized to json

/// done event ///
event: 'done';
data: 'this stream has ended';

/// heartbeat event ///
event: 'heartbeat';
data: '';
Example Usage:
// procedures/users/watchUser.rpc.ts
export default defineEventStreamRpc({
    params: a.object({
        userId: a.string(),
    }),
    response: a.object({
        id: a.string(),
        name: a.string(),
        createdAt: a.timestamp(),
        updatedAt: a.timestamp(),
    }),
    handler({ params, stream }) {
        // initialize the stream and send it to the client
        stream.send();

        // send a message every second
        const interval = setInterval(async () => {
            await stream.push({
                id: '1',
                name: 'John Doe',
                createdAt: new Date(),
                updatedAt: new Date(),
            });
        }, 1000);

        // cleanup when the client disconnects
        stream.on('close', () => {
            clearInterval(interval);
        });
    },
});

EventStreamConnection methods

// send the stream to the client. Must be called before pushing any messages
stream.send()
// push a new message to the client
stream.push(data: Data, eventId?: string)
// close the stream and tell the client that there will be no more messages
stream.close()
// register a callback that will fire after the stream has been close by the server or the connection has been dropped
stream.onClosed(cb: () => any)

Adding Non-RPC Routes

You can also add generic endpoints for instances when a message-based RPC endpoint doesn't fit.

// using the app instance
const app = new ArriApp();
app.route({
    method: "get",
    path: "/hello-world",
    handler(event) {
        return "hello world";
    },
});

// using a sub-router
const app = new ArriApp();
const router = new ArriRouter();
router.route({
    method: "get",
    path: "/hello-world",
    handler(event) {
        return "hello world",
    }
})
app.use(router)

// sup-routers can also specify a route prefix
const router = new ArriRouter("/v1")
router.route({
    method: "get",
    path: "/hello-world", // this will become /v1/hello-world
    handler(event) {
        return "hello world"
    }
});

Adding Middleware

const app = new ArriApp();

const requestLoggerMiddleware = defineMiddleware((event) => {
    console.log(`new request at ${event.path}`);
});

app.use(requestLoggerMiddleware);

Adding to the RPC Context

Any values added to event.context will become available in the rpc instance

const authMiddleware = defineMiddleware(async (event) => {
    // assume you did something to get the user from the request
    event.context.user = {
        id: 1,
        name: 'John Doe',
        email: '[email protected]',
    };
});

app.rpc('sayHello', {
    params: undefined,
    response: a.object({
        message: a.string(),
    }),
    // user is available here
    handler({ user }) {
        return {
            message: `Hello ${user.name}`,
        };
    },
});

To get type safety for these new properties create a .d.ts file and augment the ArriEventContext provided by @arrirpc/server

import '@arrirpc/server';

declare module '@arrirpc/server' {
    interface ArriEventContext {
        user?: {
            id: number;
            name: string;
            email: string;
        };
    }
}

Background Workers & Lifecycle Hooks

You can register background workers and lifecycle hooks on ArriApp. Workers and lifecycle hooks do not run when the app is imported during codegen (arri build); they only start when the web server begins listening.

import { ArriApp, defineWorker } from '@arrirpc/server';

const app = new ArriApp({
    onStart() {
        console.log('Server is starting up');
    },
    onStop() {
        console.log('Server is shutting down');
    },
    onWorkerError(err, worker) {
        logger.error({ err, worker: worker.name }, 'Worker error');
    },
});

// Registering a worker with a continuous loop and AbortSignal
app.registerWorker(
    defineWorker({
        name: 'queue-consumer',
        async start(signal) {
            while (!signal.aborted) {
                const job = await queue.pop({ signal });
                if (job) await processJob(job);
            }
        },
        async stop() {
            // Optional: cleanup or drain
        },
    }),
);

// Registering a third-party worker (e.g. BullMQ)
app.registerWorker(
    defineWorker({
        name: 'bullmq-emails',
        async start() {
            worker = new Worker('emails', async (job) => {
                await sendEmail(job.data);
            });
        },
        async stop() {
            await worker.close();
        },
    }),
);

// Function shorthand
app.registerWorker(async (signal) => {
    while (!signal.aborted) {
        await heartbeat();
        await delay(5000, signal);
    }
});

Services can also register workers:

const authService = new ArriService('auth');
authService.registerWorker(tokenCleanupWorker);

app.use(authService); // Workers will be registered on the app

Adding Client Generators

// arri.config.ts
import { defineConfig, servers, generators } from "arri";

export default defineConfig({
    server: servers.tsServer(),
    generators: [
        generators.typescriptClient({...}),
        generators.dartClient({...}),
        generators.kotlinClient({...})
        generators.someGenerator({...})
    ]
});

For info on what generators are available see here

For info on how to create your own generator see @arrirpc/codegen-utils

Key Concepts

Arri Definition File

The server generates a __definition.json file that acts as a schema for all of the procedures and models in the API. By default this schema is viewable from /__definition when the server is running, but it can be modified. The endpoint is also relative to the rpcRoutePrefix option.

It looks something like this:

{
    "procedures": {
        "sayHello": {
            "transport": "http",
            "path": "/say-hello",
            "method": "post",
            "params": "SayHelloParams",
            "response": "SayHelloResponse"
        }
        // rest of procedures
    },
    "definitions": {
        "SayHelloParams": {
            "properties": {
                "name": {
                    "type": "string"
                }
            }
        },
        "SayHelloResponse": {
            "properties": {
                "message": {
                    "type": "string"
                }
            }
        }
        // rest of models
    }
}

Arri is able to use this schema file to automatically generate clients in multiple languages. In this way it works similarly to an Open API schema, but with much better code-generation support. I've made a lot of deliberate choices in designing this schema to make code-generation easier and more consistent across languages. For example, Arri schemas use a superset of JSON Type Definition for their models instead of JSON Schema.

How Procedures Map To Endpoints

Every procedure maps to a different url based on it's name. For example given the following file structure:

|--src
   |--procedures
      |--getStatus.rpc.ts
      |--users
         |--getUser.rpc.ts
         |--updateUser.rpc.ts

We will get the following endpoints:

POST /get-status
POST /users/get-user
POST /users/update-user

(Note: these will always be relative to the `rpcRoutePrefix` option)

By default all procedures will become post requests, but you can change this when creating a procedure:

// procedures/users/getUser.rpc.ts
export default defineRpc({
    method: 'get',
    // rest of config
});

The supported HTTP methods are as follows:

  • post
  • get
  • delete
  • patch
  • put

When using a get method the RPC params will be mapped as query parameters which will be coerced into their type using the a.coerce method from arri-validate. Get methods support all basic scalar types however arrays and nested objects are not supported.

H3 Support

Arri is built on top of H3 so many of the concepts that apply to H3 also apply to Arri.

Accessing Utilities

Arri re-eports all of the H3 utilities.

import { getRequestIP, setResponseHeader } from '@arrirpc/server';

Accessing H3 Events

You can access H3 events from inside procedures handlers.

defineRpc({
    params: undefined,
    response: undefined,
    handler(_, event) {
        getRequestIP(event);
    },
});

defineEventStreamRpc({
    params: undefined,
    response: undefined,
    handler(_, event) {
        getRequestIP(event);
    },
});

Manually Starting an Arri Server

Arri server is just an H3 app under the hood so you can start it the same way you would start an H3 app. Although you should note that currently the filed based router only works when using the Arri CLI.

import { createServer } from 'node:http';
import { ArriApp, toNodeListener } from '@arrirpc/server';

const app = new ArriApp();

createServer(toNodeListener(app.h3App)).listen(process.env.PORT || 3000);

Arri CLI

# start the dev server
arri dev [flags]

# create a production build
arri build [flags]

# create a new project
arri init [dir]

# run codegen
arri codegen [path-to-definition-file]

Development

Building

Run nx build arri-rpc to build the library.

Running unit tests

Run nx test arri-rpc to execute the unit tests via Vitest.

版本与发布记录

@arrirpc/server 共发布 89 个版本,首次发布于 2024/05/14;过去 12 个月发布了 11 个版本。

版本发布日期距今
0.85.0latest2026/10/031 个月前
0.84.12026/08/043 个月前
0.84.02026/08/043 个月前
0.83.12026/07/143 个月前
0.83.02026/07/143 个月前
0.82.02026/05/225 个月前

其他发布通道:beta → 0.76.3-beta.0。

关于 @arrirpc/server 的常见问题

@arrirpc/server 是什么?

Typescript implementation of Arri RPC. It's built on top of H3 and uses esbuild for bundling.

如何安装 @arrirpc/server?

在项目目录运行 npm install @arrirpc/server,或使用 pnpm add / yarn add / bun add。

@arrirpc/server 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@arrirpc/server 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@arrirpc/server 可以商用吗?

@arrirpc/server 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@arrirpc/server 的下载量有多少?

根据 npm 官方下载统计,@arrirpc/server 在 2026/10/07 – 2026/11/05 这 30 天内被下载 4,493 次,日均约 150 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arrirpc/server 还在维护吗?

最新版本 0.85.0 发布于 2026/10/03(1 个月前);至今共发布 89 个版本,其中过去 12 个月发布了 11 个;首次发布于 2024/05/14。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。