Skip to content

IRPC Comparison

This page compares IRPC with other popular API patterns to help you choose the right tool for your project.

IRPC vs REST

AspectIRPCREST
BoilerplateZero - just declare functionsHigh - routes, controllers, serialization
Type SafetyEnd-to-end TypeScriptManual type definitions
Performance6.96x faster (batching)1x baseline
HTTP Requests10x fewer (automatic batching)One per call
Learning CurveMinimal - just functionsModerate - HTTP verbs, status codes
CachingBuilt-in per-functionManual implementation
Error HandlingAutomatic retry & timeoutManual implementation

REST Example

typescript
// Define route
app.post('/api/users', async (req, res) => {
  const data = req.body;
  const user = await db.users.create(data);
  res.json(user);
});

// Client call
const response = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'John', email: 'john@example.com' }),
});
const user = await response.json();

IRPC Example

typescript
// Declare function
const createUser = irpc.declare<CreateUserFn>({ name: 'createUser' });

// Implement handler
irpc.construct(createUser, async (data) => {
  return await db.users.create(data);
});

// Client call
const user = await createUser({ name: 'John', email: 'john@example.com' });

Result: IRPC eliminates routes, manual serialization, and HTTP boilerplate.

IRPC vs gRPC

AspectIRPCgRPC
Setup ComplexitySimple - no code generationComplex - protobuf compilation
JavaScript ErgonomicsNative - just TypeScriptForeign - proto files
Browser SupportNative fetch APIRequires gRPC-web proxy
Type SafetyTypeScript nativeGenerated types
Performance6.96x faster than RESTSimilar to IRPC
StreamingHTTP streamingBidirectional streaming
BatchingAutomaticManual

gRPC Example

protobuf
// user.proto
syntax = "proto3";

service UserService {
  rpc CreateUser (CreateUserRequest) returns (User);
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
}
typescript
// Generated code required
const client = new UserServiceClient('localhost:50051');
const user = await client.createUser({ name: 'John', email: 'john@example.com' });

IRPC Example

typescript
// No proto files, no code generation
const createUser = irpc.declare<CreateUserFn>({ name: 'createUser' });
const user = await createUser({ name: 'John', email: 'john@example.com' });

Result: IRPC provides gRPC-like performance without the complexity.

IRPC vs tRPC

AspectIRPCtRPC
Transport FlexibilityAny transport (HTTP, WebSocket, custom)HTTP, WebSocket (via subscriptions)
BatchingAutomatic, configurableAutomatic (via links)
SetupPackage + transportRouter + client
Type SafetyTypeScript nativeTypeScript native
Performance6.96x faster than RESTSimilar to IRPC
MiddlewareTransport-levelProcedure-level
CachingBuilt-in per-functionClient-side (manual or via React Query)

tRPC Example

typescript
// Define router
const appRouter = router({
  createUser: procedure
    .input(z.object({ name: z.string(), email: z.string().email() }))
    .mutation(async ({ input }) => {
      return await db.users.create(input);
    }),
});

// Client call (vanilla)
const user = await trpc.createUser.mutate({ 
  name: 'John', 
  email: 'john@example.com' 
});

IRPC Example

typescript
// Declare function
const createUser = irpc.declare<CreateUserFn>({ name: 'createUser' });

// Client call
const user = await createUser({ name: 'John', email: 'john@example.com' });

Result: Both are type-safe and framework-agnostic. Both have built-in validation (IRPC opt-in, tRPC integrated). IRPC has simpler function-based API, tRPC has router-based API.

IRPC vs GraphQL

AspectIRPCGraphQL
Query ComplexitySimple function callsComplex query language
Type GenerationNative TypeScriptCode generation required
CachingPer-function, simpleNormalized cache, complex
Over-fetchingNo - exact function returnsNo - query what you need
Under-fetchingBatching handles multiple callsSingle query for nested data
Learning CurveMinimal - just functionsSteep - schema, resolvers, queries
Performance6.96x faster than RESTSimilar to REST
N+1 ProblemNo - batchingRequires DataLoader

GraphQL Example

graphql
# Schema definition
type User {
  id: ID!
  name: String!
  email: String!
}

type Mutation {
  createUser(name: String!, email: String!): User!
}
typescript
// Resolver implementation
const resolvers = {
  Mutation: {
    createUser: async (_, { name, email }) => {
      return await db.users.create({ name, email });
    },
  },
};

// Client call
const { data } = await client.mutate({
  mutation: gql`
    mutation CreateUser($name: String!, $email: String!) {
      createUser(name: $name, email: $email) {
        id
        name
        email
      }
    }
  `,
  variables: { name: 'John', email: 'john@example.com' },
});

IRPC Example

typescript
// Declare function
const createUser = irpc.declare<CreateUserFn>({ name: 'createUser' });

// Client call
const user = await createUser({ name: 'John', email: 'john@example.com' });

Result: IRPC is simpler and doesn't require learning GraphQL query language.

Performance Benchmark

Scenario: 100,000 users, 10 calls each (1,000,000 total calls)

FrameworkTotal TimeHTTP RequestsSpeedup
IRPC3,617ms100,0006.96x 🚀
Bun Native25,180ms1,000,0001.00x
Hono18,004ms1,000,0001.40x
Elysia36,993ms1,000,0000.68x

IRPC's automatic batching reduces HTTP overhead by 10x, resulting in 6.96x faster performance.

When to Use IRPC

Choose IRPC when:

  • ✅ You want type-safe remote calls without boilerplate
  • ✅ You need high performance with automatic batching
  • ✅ You prefer simple function calls over complex query languages
  • ✅ You want transport flexibility (HTTP, WebSocket, custom)
  • ✅ You're building a TypeScript/JavaScript application
  • ✅ You want built-in caching, retry, and timeout

Choose REST when:

  • You need broad client compatibility (non-JavaScript)
  • You're building a public API with strict HTTP semantics
  • You have existing REST infrastructure

Choose gRPC when:

  • You need bidirectional streaming
  • You're in a polyglot microservices environment
  • You have strict performance requirements for internal services

Choose tRPC when:

  • You're building a React application with React Query
  • You want type safety without transport flexibility
  • You're okay with framework coupling

Choose GraphQL when:

  • You need flexible, client-driven queries
  • You have complex, nested data relationships
  • You want to expose a single endpoint for multiple clients

Migration Path

From REST to IRPC

  1. Replace route definitions with IRPC function declarations
  2. Convert controllers to handlers
  3. Remove manual serialization logic
  4. Update client fetch calls to function calls

From gRPC to IRPC

  1. Replace proto files with TypeScript types
  2. Convert service definitions to IRPC declarations
  3. Keep existing handler logic
  4. Remove protobuf compilation step

From tRPC to IRPC

  1. Replace router procedures with IRPC declarations
  2. Remove React Query dependency (if desired)
  3. Keep existing handler logic
  4. Update client calls to direct function calls

From GraphQL to IRPC

  1. Replace schema definitions with TypeScript types
  2. Convert resolvers to IRPC handlers
  3. Replace queries/mutations with function calls
  4. Remove GraphQL client dependency

Summary

IRPC combines the best of all worlds:

  • Simplicity of REST
  • Performance of gRPC
  • Type safety of tRPC
  • Flexibility without GraphQL complexity

Choose IRPC when you want high-performance, type-safe remote calls without the complexity of other solutions.