When it comes to building scalable and production-ready APIs, developers often turn to established frameworks like GraphQL Federation. However, as any seasoned developer can attest, these tools can come with their own set of challenges and complexities. In this article, we’ll delve into the world of tRPC, a revolutionary new approach to building APIs that’s gaining traction in the industry.

The Problem with Traditional API Architectures
Traditional API architectures, such as those built using GraphQL Federation, can be cumbersome and difficult to maintain. One of the primary concerns is schema synchronization, which can become a nightmare as the number of services and dependencies grows. In a typical GraphQL Federation setup, schema changes require a convoluted process involving code generation, resolver updates, and client-side code regeneration. This not only leads to increased complexity but also introduces the risk of schema-related issues that can manifest in production.
Introducing tRPC: A New Paradigm for API Development
tRPC (tClient) is an innovative approach to building APIs that eliminates the need for schema definitions, thereby reducing the risk of API bugs and improving overall performance. By leveraging TypeScript, tRPC enables developers to create robust and scalable APIs that are free from the schema-related issues plaguing traditional architectures. In this article, we’ll explore the essential steps required to build production-ready tRPC APIs with TypeScript.
Step 1: Setting Up the Development Environment
Before diving into the world of tRPC, it’s essential to set up a development environment that’s conducive to building scalable and production-ready APIs. This involves installing the necessary dependencies, including the tRPC library, and configuring the project structure. To get started, create a new Next.js 14 project using the following command:
npx create-next-app my-app --ts
Step 1.1: Installing tRPC and Related Dependencies
Next, install the tRPC library and related dependencies using npm or yarn:
npm install @trpc/client @trpc/server
Step 1.2: Configuring the Project Structure
Configure the project structure by creating a new folder for the API and adding the necessary files, including the `trpc.ts` file that will serve as the central hub for API definitions.
Step 2: Defining API Endpoints with tRPC
With the development environment set up, it’s time to define API endpoints using tRPC. This involves creating a new file, `api.ts`, where you’ll define the API endpoints using the `createTRPCClient` function.
Step 2.1: Defining API Endpoints
Define API endpoints using the `createTRPCClient` function, which takes in the `api` instance and returns a client object that can be used to make API calls:
import { createTRPCClient } from '@trpc/client';
import { httpLink } from '@trpc/client';
import { createNextApiHandler } from 'next';
import { trpc } from './trpc';
export const appRouter = trpc.router().query('get-users', {
async resolve() {
return [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
];
},
}).mutation('create-user', {
input: z.object({
id: z.string(),
name: z.string(),
}),
async resolve({ input }) {
// Create a new user
return { id: input.id, name: input.name };
},
});
export default createNextApiHandler({
router: appRouter,
// Add middlewares or configuration options as needed
});
Step 3: Implementing API Routing with Next.js 14 App Router
With the API endpoints defined, it’s time to implement API routing using Next.js 14 App Router. This involves creating a new file, `api/route.ts`, where you’ll define the API routes using the `appRouter` instance.
You may also enjoy reading: Google Gemini Finally Runs on a Single Air-Gapped Server, Then Disappears Forever.
Step 3.1: Defining API Routes
Define API routes using the `appRouter` instance, which provides a simple and declarative way to define API endpoints:
import { appRouter } from './api';
export default appRouter.createRoute({
method: 'GET',
path: '/users',
}).handler(async () => {
const users = await appRouter.query('get-users')();
return users;
});
export default appRouter.createRoute({
method: 'POST',
path: '/users',
}).handler(async ({ req }) => {
const userInput = req.body;
const user = await appRouter.mutation('create-user')({ input: userInput });
return user;
});
Step 4: Handling API Errors and Logging
When building production-ready APIs, it’s essential to handle API errors and logging. This involves implementing error-handling middleware and logging mechanisms to ensure that errors are properly handled and logged.
Step 4.1: Implementing Error-Handling Middleware
Implement error-handling middleware using Next.js 14 App Router’s built-in `onError` function:
import { NextApiRequest, NextApiResponse } from 'next';
import { appRouter } from './api';
appRouter.createRoute({
method: 'GET',
path: '/users',
}).handler(async (req: NextApiRequest, res: NextApiResponse) => {
try {
const users = await appRouter.query('get-users')();
return users;
} catch (error) {
return res.status(500).json({ message: 'Internal Server Error' });
}
}).onError((error) => {
// Log the error using a logging library like Winston
console.error(error);
// Return a default error response
return res.status(500).json({ message: 'Internal Server Error' });
});
Step 5: Optimizing API Performance with Caching and Code Splitting
Optimizing API performance is crucial for building scalable and production-ready APIs. This involves implementing caching and code splitting to reduce API call latency and improve overall performance.
Step 5.1: Implementing Caching
Implement caching using a library like Redis or a caching layer like Varnish Cache:
import { appRouter } from './api';
appRouter.createRoute({
method: 'GET',
path: '/users',
}).handler(async (req, res) => {
// Check if the user is cached
const cachedUser = await Redis.get(`user:${req.query.userId}`);
if (cachedUser) {
return cachedUser;
}
// Fetch the user from the database
const user = await db.query('SELECT * FROM users WHERE id =?', [req.query.userId]);
// Cache the user for 1 hour
Redis.set(`user:${req.query.userId}`, user, 'EX', 3600);
return user;
});
Step 5.2: Implementing Code Splitting
Implement code splitting using Next.js 14 App Router’s built-in `getStaticProps` function:
import { NextApiRequest, NextApiResponse } from 'next';
import { appRouter } from './api';
export async function getStaticProps() {
// Fetch the users from the database
const users = await db.query('SELECT * FROM users');
// Return the users as static props
return {
props: {
users,
},
};
}
appRouter.createRoute({
method: 'GET',
path: '/users',
}).handler(async (req: NextApiRequest, res: NextApiResponse) => {
// Return the static props
return res.json({ users });
});





