# RPC URL: /docs/features/rpc Learn how to make type-safe API calls from your client to your server with Hono RPC. ## Creating a new API endpoint You can make a new API endpoint within the `/apps/web/src/server/router` directory. ```ts title="/apps/web/src/server/router/example/router.ts" import { getFactory } from "@/server/router/factory"; export const exampleRouter = getFactory() .createApp() .get("/", async (c) => { return c.json({ ok: true }, 200); }); ``` If you've added a new router, you will need to ensure that the route contains a path to the router you defined in `/apps/web/src/app/api/[[...route]]/route.ts`. ```ts title="/apps/web/src/app/api/[[...route]]/route.ts" const app = new Hono<{ Bindings: CloudflareEnv }>() .basePath("/api") .use(cors) .route("/example", router.example); ``` ### Protecting an endpoint You can protect an endpoint by adding the `isAuthenticated` middleware to the router. ```ts title="/apps/web/src/server/router/example/router.ts" import { getFactory } from "@/server/router/factory"; import { isAuthenticated } from "@/server/router/middleware"; export const exampleRouter = getFactory() .createApp() .get("/", isAuthenticated({ id: "example" }), async (c) => { // If `isAuthenticated` is provided, the user will be available // in the context variable `c.var` const { user } = c.var; return c.json({ ok: true, user }, 200); }); ``` ## Making an API call ```tsx title="hono.ts" import { rpc } from "@/lib/rpc"; // This makes a GET request to the /api/teams endpoint const res = await rpc.api.teams.$get(); if (res.status !== 200 || !res.ok) { throw new Error("Failed to fetch teams"); } const teams = await res.json().then((data) => data.teams); ``` ### Querying in React ```tsx title="page.tsx" "use client"; import { useQuery } from "@tanstack/react-query"; import { rpc } from "@/lib/rpc"; import type { FC } from "react"; const Page: FC = () => { const teams = useQuery({ // This is a utility to create a query key, which is used to cache // the result queryKey: rpc.$key(rpc.api.teams), queryFn: async () => { // This makes a GET request to the /api/teams endpoint const res = await rpc.api.teams.$get(); if (res.status !== 200 || !res.ok) { throw new Error("Failed to fetch teams"); } return res.json().then((data) => data.teams); }, }); return (
{teams.isPending &&

Loading...

} {teams.isError &&

Error: {teams.error.message}

} {teams.isSuccess && ( )}
); }; export default Page; ``` ### Mutating in React ```tsx title="page.tsx" "use client"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { rpc } from "@/lib/rpc"; import type { InferRequestType } from "hono"; import type { FC } from "react"; interface PageProps { params: Promise<{ teamSlug: string }>; } const Page: FC = async (props) => { const { teamSlug } = await props.params; const queryClient = useQueryClient(); // This makes a PATCH request to the /api/teams/:urlSlug endpoint const updateFn = rpc.api.teams[":urlSlug"].$patch; const updateTeam = useMutation({ mutationFn: async (input: InferRequestType) => { const res = await updateFn(input); if (res.status !== 200 || !res.ok) { throw new Error("Failed to update team"); } const updated = res.json().then((data) => data.team); console.log("Updated team:", updated); }, // This is used to invalidate the query cache for the teams endpoint onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: rpc.$key(rpc.api.teams), }); }, }); const handleUpdate = async () => { await updateTeam.mutateAsync({ // This specifies the :urlSlug parameter in the endpoint // The type is inferred from the endpoint definition // so you get type safety here param: { urlSlug: teamSlug }, // This is the request body // The type is also inferred from the endpoint definition // so you get type safety here as well json: { name: "My new team" }, }); }; return
{/* ... */}
; }; ``` ## TypeScript performance TypeScript performance is a common concern when using heavily type-inferred libraries. Hono RPC is no exception. As you define more endpoints, you may notice a slowdown in your IDE's performance, particularly with intellisense and type checking. When working within the `@workspace-apps/web` app, all of your types should be automatically inferred between the backend and frontend; however, you can run `pnpm hono:generate` to statically generate the types for your endpoints. Once you run this command, the generated RPC with flat types will be made available in `/apps/web/hono/rpc`. You can continue using the rpc with fully inferred types, but if you find that your IDE is starting to struggle with performance, you can switch to rpc with flat types by importing from the generated rpc instead. ```bash title="/apps/web" pnpm hono:generate ``` ```tsx title="page.tsx" // rpc with generated flat types // this is extremely performant, but requires you to run `pnpm hono:generate` import { rpc } from "@/hono/rpc"; // rpc with fully inferred types // this may show down your Next.js dev server and IDE performance, // but it does not require you to run `pnpm hono:generate` import { rpc } from "@/lib/rpc"; ``` ## Why not tRPC? [tRPC](https://trpc.io/) is a fantastic library for building type-safe APIs in TypeScript, and there are no reasons why you shouldn't use it if you enjoy its API. However, there are a few reasons why we chose to use Hono RPC instead. ### Built on Web Standards Hono RPC is built on top of [Web Standards](https://hono.dev/docs/concepts/web-standard), notably the Fetch API, and includes basic HTTP primitives like Request, Response, URL and Headers. Building on top of these standards makes it easier to follow tutorials and documentation that are based on these standards, and makes it more interoperable with other libraries and tools in the JavaScript and web ecosystems. ### Type Safety Like tRPC, Hono RPC allows you to share types between your client and server, ensuring that your API calls are type-safe end-to-end. ```ts twoslash title="example.ts" import { zValidator as zv } from "@hono/zod-validator"; import { Hono } from "hono"; import { hc } from "hono/client"; import { z } from "zod"; // server const app = new Hono().basePath("/api").post( "/teams", zv("json", z.object({ name: z.string() })), (c) => { const { name } = c.req.valid("json"); const created = { id: "123", name }; return c.json({ team: created }, 200); }, ); // client const rpc = hc("http://localhost:3000"); const team = await rpc.api.teams.$post({ // This makes a POST request to the /api/teams endpoint // json is the request body and is type-safe json: { name: "My new team" }, }) // res is a type-safe standard Fetch Response .then((res) => res.status === 200 ? res.json() : null) .then((data) => data?.team ?? null); ``` Not only that, but each HTTP status code is also type-safe, so you can return different data for each status code, and the client will differentiate between them via [TypeScript discriminated unions](https://zod.dev/api?id=discriminated-unions). ### Type Performance tRPC has been designed to always be type-inferred, which can lead to performance issues in larger applications as more procedures are added. For instance, [one such user](https://github.com/trpc/trpc/discussions/5508) cites that resolving types takes 4-8 seconds on a M1 Max and 32 GB of RAM, with [another reply](https://github.com/trpc/trpc/discussions/5508#discussioncomment-8878965) citing that casting the entire router to `any` resolves performance issues. Meanwhile, when you create a new Hono RPC and provide it your Hono app type, the Hono client will strip all backend-specific types (e.g. your [server's context](https://trpc.io/docs/server/context)) and only retains the types that are relevant to the client. This means that you can start with fully inferred types, which is great for development; then you can statically generate flat types later on as your application scales to improve TypeScript performance in your IDE. #### Trade-offs * Because the Hono client strips all backend-specific types, you do lose the ability to cmd+click into the backend code from the client. While that is inconvenient, your routes should be easy to find in code with [good routing organization](https://hono.dev/docs/api/routing#grouping) (the [ship.pluv.io](https://ship.pluv.io) templates do this well). * tRPC does have solutions like [xtrpc](https://github.com/algora-io/xtrpc), which users have created to solve tRPC performance issues. However, these solutions are not an official part of tRPC and can be unreliable as a result. ## tRPC vs Hono RPC | | [tRPC](https://trpc.io/) | [Hono](https://hono.dev/) | | ----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | | npm | [npm](https://www.npmjs.com/package/@trpc/server) | [npm](https://www.npmjs.com/package/hono) | | GitHub | [https://github.com/trpc/trpc](https://github.com/trpc/trpc) | [https://github.com/honojs/hono](https://github.com/honojs/hono) | | Built on Web Standards | 🪄 No. Abstracts away standard APIs | 🛠️ Yes. Builds around standard APIs | | End-to-end type-safety | 🛡️ Yes | 🛡️ Yes | | IDE-performant at scale | 🐌 No | ⚡ Yes, when using flat types. Else no | | Can build flat types | 🛑 No. At least would be very difficult | ✅ Yes. Can simply use tsc | | Can cmd+click to server | 🖱️ Yes. Goes straight to the procedure | 🔎 No. Must search for the endpoint in code | | Works with React Query | ✅ Yes | ✅ Yes | | Client/Server coupling | 🔗 Tightly coupled | 🌱 Uncoupled. Can be in different repos and different languages | | Routing model | Procedure-based | HTTP verb and path-based |