Beyond REST and GRAPHQL: Why You Should Consider RPC (and Why tRPC Makes It Easy)

In the ever-evolving landscape of web development, choosing the right architecture for server-client communication is crucial for building efficient, scalable, and maintainable applications. While REST (Representational State Transfer) and GraphQL have become the go-to standards, there's a growing interest in an alternative approach that's been around but is gaining fresh attention: Remote Procedure Call (RPC), particularly with the emergence of tools like tRPC. In this blog, we'll explore the reasons you should consider RPC for your next project and how tRPC simplifies its implementation, making it an attractive choice for developers.

Understanding RPC

RPC is a protocol that allows a program to execute a procedure (function) on a remote server as if it were local. This concept is straightforward and powerful: it abstracts the complexity of network communication, allowing developers to focus on business logic rather than the intricacies of data transfer over the network.

Why Consider RPC?

1. Simplicity and Directness: RPC models closely mirror the way we naturally think about executing functions in code. There's no need to map operations to HTTP methods or design complex query languages. You call a function with parameters, and it returns a result. This direct mapping between actions and server-side functions can lead to cleaner, more intuitive codebases.

2. Efficiency and Performance: RPC frameworks often use more efficient data serialization formats, such as Protocol Buffers, which can lead to faster data transfer compared to the text-based formats like JSON used in REST and GraphQL. This efficiency makes RPC particularly suited for high-performance applications and microservices architectures where every millisecond counts.

3. Strong Typing and Compile-Time Checks: Many RPC frameworks, including tRPC, support or enforce type safety. This means errors can be caught at compile time rather than runtime, significantly reducing bugs and improving the developer experience through better tooling and autocompletion.

4. Language Agnosticism: RPC does not tie you to a specific programming language. With the right tools, you can generate client and server stubs in many languages from a single interface definition. This flexibility is invaluable in polyglot environments where different services are written in different languages.

Introducing tRPC

tRPC stands for TypeScript RPC, and it combines the benefits of RPC with the advantages of TypeScript, providing an end-to-end type-safe API layer. Here's why tRPC is making RPC more accessible and appealing. it is an open-source library designed to enable end-to-end typesafe APIs without the need for generating or maintaining additional types or schemas. In essence, it allows developers to create APIs where the types are inferred automatically through TypeScript, making the development process smoother and reducing the risk of type-related bugs.

How Does tRPC Work?

tRPC operates on the principle of Remote Procedure Calls (RPC), where functions defined on the server can be called directly from the client as if they were local functions, with TypeScript ensuring that the inputs and outputs of these functions are correctly typed.

Why Use tRPC?

Simplified Data Fetching

With tRPC, the complexity of data fetching is significantly reduced. You no longer have to worry about maintaining separate type definitions for your frontend and backend or dealing with the overhead of traditional REST or GraphQL APIs.

Enhanced Developer Experience

Developers can enjoy a streamlined workflow where the types for their API calls are automatically inferred, leading to faster development cycles and fewer bugs.

Reduced Boilerplate

tRPC's approach eliminates the need for writing and maintaining schemas or DTOs (Data Transfer Objects), reducing boilerplate and keeping your codebase cleaner and more maintainable.

Seamless Integration with Existing Tools

tRPC is designed to work seamlessly with popular frameworks like Next.js and tools like React Query or SWR, allowing you to integrate it into your projects without overhauling your existing setup.

Setting Up a tRPC Application

  1. Define Procedures: On the server, you define procedures, which are essentially TypeScript functions that can perform CRUD operations, interact with a database, or handle business logic.

  2. Create tRPC Router: These procedures are then added to a tRPC router, which organizes your API endpoints in a way that's easily accessible to the client.

  3. Consume API on the Client: On the client side, you consume these procedures directly, with TypeScript inferring the types automatically, ensuring that the data you work with is always expected and correct.

Example: Creating a tRPC router for handling item operations (src/server/routers/itemRouter.ts):

import { z } from 'zod';
import { createTRPCRouter, protectedProcedure } from '../trpc';

export const itemRouter = createTRPCRouter({
    get: protectedProcedure
        .input(
            z.object({
                id: z.number(),
            }),
        )
        .query(({ input, ctx }) => {
            return ctx.prisma.item.findUnique({
                where: {
                    id: input.id,
                },
            });
        }),
});

in the above, The createTRPCRouter function is used to define a tRPC router. A router in tRPC organizes your procedures, which can be queries (for fetching data) or mutations (for data modifications). In this case, itemRouter is a router that contains a single procedure named get

Once the tRPC client is set up, you can consume the itemRouter API in a Next.js component using the useQuery hook. Here's an example component that fetches an item by its ID:

import { trpc } from '../utils/trpc'; // Adjust the import path based on your setup

const ItemComponent = ({ itemId }: { itemId: number }) => {
  // Use the tRPC useQuery hook to call the get procedure in itemRouter
  const { data: item, isLoading, error } = trpc.item.get.useQuery({
    id: itemId,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>An error occurred: {error.message}</div>;
  if (!item) return <div>No item found.</div>;

  return (
    <div>
      <h1>Item Details</h1>
      <p>ID: {item.id}</p>
      <p>Name: {item.name}</p>
      // Add more item details as needed
    </div>
  );
};

export default ItemComponent;

This setup provides a seamless, typesafe way to fetch and display data from your backend using tRPC in a Next.js application. The combination of Next.js and tRPC offers a powerful framework for building typesafe full-stack applications with TypeScript, enhancing developer productivity and application reliability.

Here are some important tRPC terms to know:

1. Typesafety

Typesafety in the context of tRPC means that the API calls between the client and server are fully type-checked by TypeScript. This ensures that the data sent and received matches the expected types, reducing runtime errors and improving developer productivity through better tooling and code completion.

2. Router

In tRPC, a router is an object that organizes and exposes your server-side procedures (functions) to be called from the client. Routers can be nested and combined to structure your application's API logically.

3. Procedure

A procedure in tRPC is a function defined on the server that can be called by the client. Procedures are the core building blocks of a tRPC API and can handle various operations such as fetching data, submitting forms, or executing server-side logic.

4. Query

A query in tRPC is a type of procedure designed to fetch data. It is similar to a GET request in a REST API or a query in GraphQL. Queries are read-only and do not modify server-side data.

5. Mutation

A mutation is another type of procedure in tRPC, used for operations that change server-side data, similar to POST, PUT, DELETE requests in REST, or mutations in GraphQL. Mutations are used for creating, updating, or deleting data.

6. Zod

Zod is a TypeScript-first schema declaration and validation library often used with tRPC to define the shape and validation rules for inputs and outputs of procedures. tRPC leverages Zod to ensure that data conforms to specified schemas, enhancing typesafety.

7. Context (ctx)

In tRPC, context (often abbreviated as ctx) refers to an object passed through to all procedures that can contain request-specific information, such as user authentication details, enabling access control and other context-aware logic within procedures.

8. Adapters

Adapters in tRPC allow the framework to be integrated with various server and client environments. For the server, it could be Express, Fastify, or Next.js. For the client, adapters exist for React, Svelte, Vue, etc. They ensure tRPC works seamlessly across different tech stacks.

10. Type Inference

Type inference is a feature of TypeScript and tRPC that allows the types of inputs and outputs to be automatically determined without explicitly declaring them. In tRPC, this means the client automatically knows the types based on the server-side definitions, ensuring that the frontend and backend are always in sync.

Disadvantages of using tRPC

While tRPC offers numerous advantages for building typesafe APIs, especially in TypeScript-based applications, it's important to consider its limitations and potential drawbacks. Understanding these shortcomings can help developers make informed decisions about when and how to use tRPC in their projects. Here are some of the main challenges and limitations associated with tRPC:

1. TypeScript Dependency

Limited to TypeScript Projects: tRPC is designed to leverage TypeScript's type system for end-to-end typesafety. While this is a strength for TypeScript projects, it means that tRPC is not suitable for projects that use plain JavaScript or other programming languages.

2. Learning Curve

New Concepts and Practices: Developers unfamiliar with TypeScript, RPC, or the specific conventions of tRPC may face a learning curve. Understanding how to structure applications using routers, procedures, and the typesafe client-server communication model requires time and effort.

3. Ecosystem Compatibility

Integration with Existing Tools: While tRPC works well within the TypeScript ecosystem and has adapters for popular frameworks, there may be challenges when integrating it with certain third-party libraries or tools that expect a REST or GraphQL API. This could require additional work to bridge the compatibility gaps.

4. Community and Resources

Smaller Community: Compared to more established technologies like REST or GraphQL, tRPC's community is smaller, which can affect the availability of learning resources, third-party integrations, and community support. However, the community is growing as tRPC gains popularity.

5. Performance Considerations

Overhead and Optimization: Like any abstraction, tRPC introduces its own set of overheads. While it simplifies typesafe API development, there may be scenarios where the additional abstraction layer could impact performance, especially in highly optimized or resource-constrained environments.

6. Flexibility vs. Convention

Opinionated Approach: tRPC has an opinionated approach to API design, centered around TypeScript's capabilities. While this provides many benefits in terms of typesafety and development speed, it may not offer the same level of flexibility as REST or GraphQL in certain use cases, particularly when dealing with complex data relationships or when fine-grained control over the API's structure is required.

7. Error Handling and Debugging

Error Propagation: Properly handling and debugging errors across the client-server boundary can require additional consideration. Ensuring that error information is accurately conveyed and handled in a typesafe manner can add complexity to the development process.

Conclusion

In conclusion, as we explore beyond REST and GraphQL, the RPC protocol, particularly through the lens of tRPC, offers a compelling approach to API development. Its emphasis on simplicity, directness, and typesafety—coupled with the power of TypeScript—makes it an invaluable tool for modern web developers. By choosing RPC and embracing tRPC, developers can enjoy a more streamlined, efficient, and error-resistant development process, ultimately leading to better, more reliable applications. As the web continues to evolve, technologies like tRPC ensure that developers have the tools they need to keep pace, pushing the boundaries of what's possible in web application development.