Building Real-Time Web applications with tRPC: A Comprehensive Guide

In today’s digital world, real-time web applications have become increasingly popular, enabling users to experience instant updates and seamless interactions. One powerful tool that developers can leverage to build real-time web apps is tRPC. With tRPC, developers can create fully type-safe APIs using TypeScript, Next.js, Chakra UI, and Prisma ORM. This comprehensive guide will walk you through the process of building a full-stack CRUD application using these cutting-edge technologies.

Table of Contents

  1. Introduction to Real-Time Web Apps and tRPC
  2. Prerequisites for Building a Real-Time Web applications with tRPC
  3. Setting Up the Project with Next.js and TypeScript
  4. Building the Backend with tRPC and Prisma ORM
  5. Creating the Server with tRPC
  6. Setting Up the Database with Prisma ORM
  7. Defining the API Routes with tRPC
  8. Implementing CRUD Operations with tRPC
  9. Building the Frontend with Next.js and Chakra UI
  10. Creating the User Interface with Chakra UI
  11. Fetching Data from the Backend with tRPC and React Query
  12. Implementing Real-Time Updates with tRPC and WebSockets
  1. Introduction to Real-Time Web Applications and tRPC

Real-time web applications have revolutionized the way users interact with web content, providing instant updates and real-time collaboration. These applications are built using frameworks and technologies that enable real-time communication between the client and server. One such technology is tRPC, a powerful toolkit that allows developers to create fully type-safe APIs using TypeScript.

tRPC offers several benefits for building real-time web apps. It provides type safety throughout the development process, ensuring that data is properly validated and eliminating common runtime errors. By using TypeScript, developers can catch errors at compile time and improve code quality. Additionally, tRPC simplifies API development by eliminating the need for manual schema management or code generation.

  1. Prerequisites for Building a Real-Time Web App with tRPC

Before diving into building a real-time web app with tRPC, there are a few prerequisites you should have in place. First, make sure you have Node.js installed on your machine, preferably version 14 or higher. You’ll also need npm or yarn as your package manager. Lastly, a text editor like Visual Studio Code will come in handy for writing and editing code.

  1. Setting Up the Project with Next.js and TypeScript

To kickstart your project, we’ll use Next.js, a popular React framework for building server-side rendered and static web applications. Next.js provides an excellent foundation for building Real-Time Web applications with tRPC.

To create a new Next.js project, open your terminal and run the following command:

npx create-next-app@12 –typescript trpc-next-prisma-todo-app

Once the project is created, navigate to the project directory and open it in your preferred text editor.

  1. Building the Backend with tRPC and Prisma ORM

Now it’s time to set up the backend of our Real-Time Web applications using tRPC and Prisma ORM. tRPC will handle the API layer, while Prisma ORM will provide an interface for interacting with the database.

To get started, create a new folder named server in the root directory of your project. This folder will contain all the server-side code for our application. Inside the server folder, create the following files: trpc.ts, context.ts, prisma.ts, and env.js.

In context.ts, we’ll define the context object that will be passed to all tRPC resolvers. The context object can contain shared data or services that need to be accessed by multiple resolvers. In our case, we’ll keep it simple and initialize an empty context.

import * as trpc from ‘@trpc/server’;

import * as trpcNext from ‘@trpc/server/adapters/next’;

interface CreateContextOptions {

// Define any shared services or data here

}

export async function createContextInner(_opts: CreateContextOptions) {

return {};

}

export type Context = trpc.inferAsyncReturnType<typeof createContextInner>;

export async function createContext(

opts: trpcNext.CreateNextContextOptions,

): Promise<Context> {

const { req, res } = opts;

return await createContextInner({ req, res });

}

In trpc.ts, we’ll initialize tRPC and define the router and procedures for our API. The router is responsible for handling incoming requests and mapping them to the appropriate procedures. Procedures define the operations that can be performed on our data.

import { Context } from ‘./context’;

import { initTRPC } from ‘@trpc/server’;

import superjson from ‘superjson’;

 

const t = initTRPC.context<Context>().create({ transformer: superjson });

 

export const router = t.router;

export const publicProcedure = t.procedure;

export const middleware = t.middleware;

export const mergeRouters = t.mergeRouters;

In prisma.ts, we’ll initialize the Prisma client, which will provide us with a convenient way to interact with the database.

import { env } from ‘./env’;

import { PrismaClient } from ‘@prisma/client’;

 

const prismaGlobal = global as typeof global & { prisma?: PrismaClient };

 

export const prisma: PrismaClient =

prismaGlobal.prisma || new PrismaClient({ log: env.NODE_ENV === ‘development’ ? [‘query’, ‘error’, ‘warn’] : [‘error’] });

 

if (env.NODE_ENV !== ‘production’) {

prismaGlobal.prisma = prisma;

}

In env.js, we’ll define the environment variables required for our application. This file will be used to store sensitive information such as database URLs and API keys.

const { z } = require(‘zod’);

const envSchema = z.object({

DATABASE_URL: z.string().url(),

NODE_ENV: z.enum([‘development’, ‘test’, ‘production’]),

});

const env = envSchema.safeParse(process.env);

if (!env.success) {

console.error(‘❌ Invalid environment variables:’, env.error);

process.exit(1);

}

module.exports.env = env.data;

With the backend set up, we can move on to defining the API routes and implementing CRUD operations with tRPC.

  1. Creating the Server with tRPC

To create the server with tRPC, we’ll define the API routes and their corresponding procedures. This will allow clients to perform CRUD operations on our data.

Create a new folder named route inside the server folder. Inside the route folder, create a file named app.router.ts. This file will contain the router for our API.

import { publicProcedure, router } from ‘../trpc’;

import { todoRouter } from ‘./todo.router’;

export const appRouter = router({

healthcheck: publicProcedure.query(() => ‘API is up and running!’),

todo: todoRouter,

});

 

export type AppRouter = typeof appRouter;

In app.router.ts, we define the healthcheck procedure, which is a simple query that returns a string to indicate that the API is functioning correctly. We also import and include the todoRouter, which we’ll implement next.

  1. Setting Up the Database with Prisma ORM

Before we proceed with implementing the CRUD operations, let’s set up the database using Prisma ORM. Prisma ORM provides a convenient way to define and interact with database models.

To get started, make sure you have the Prisma CLI installed globally on your machine. You can install it by running the following command:

npm install -g prisma

Next, create a new file named schema.prisma in the root directory of your project. In this file, we’ll define our database schema. For this tutorial, we’ll create a simple Todo model with an id and text field.

model Todo {

id    Int     @id @default(autoincrement())

text  String

}

Save the file and generate the Prisma client by running the following command:

npx prisma generate

This command will generate the @prisma/client package, which we’ll use to interact with the database in our tRPC procedures.

With the database set up, we can now implement the CRUD operations using tRPC.

  1. Defining the API Routes with tRPC

In this step, we’ll define the API routes for our application using tRPC. This will allow clients to perform CRUD operations on our Todo data model.

Create a new folder named todo inside the route folder. Inside the todo folder, create a file named todo.router.ts. This file will contain the router and procedures for our Todo API.

import { router, publicProcedure } from ‘../trpc’;

import { z } from ‘zod’;

import { prisma } from ‘../prisma’;

export const todoRouter = router({

getAll: publicProcedure.query(() => {

return prisma.todo.findMany();

}),

create: publicProcedure

.input(z.object({ text: z.string() }))

.mutation(async ({ input }) => {

return prisma.todo.create({ data: { text: input.text } });

}),

update: publicProcedure

.input(z.object({ id: z.number(), text: z.string() }))

.mutation(async ({ input }) => {

return prisma.todo.update({ where: { id: input.id }, data: { text: input.text } });

}),

delete: publicProcedure

.input(z.number())

.mutation(async ({ input }) => {

return prisma.todo.delete({ where: { id: input } });

}),

});

In todo.router.ts, we define the following procedures:

  • getAll: A query that returns all Todo items from the database.
  • create: A mutation that creates a new Todo item in the database.
  • update: A mutation that updates an existing Todo item in the database.
  • delete: A mutation that deletes a Todo item from the database.

Each procedure is defined using tRPC’s publicProcedure function, which ensures that the procedure is accessible to clients and handles input validation.

With the API routes defined, we can now move on to building the frontend of our Real-Time Web applications using Next.js and Chakra UI.

  1. Building the Frontend with Next.js and Chakra UI

In this step, we’ll set up the frontend of our real-time web app using Next.js and Chakra UI. Next.js provides an excellent foundation for building server-side rendered and static web applications, while Chakra UI offers a set of accessible and customizable UI components.

To get started, create a new folder named client in the root directory of your project. Inside the client folder, create a new Next.js page named index.tsx. This will serve as the main entry point for our frontend application.

import { VStack, Heading, Text } from ‘@chakra-ui/react’;

 

const Home = () => {

return (

<VStack spacing={4} align=”center”>

<Heading>Welcome to the Real-Time Todo App!</Heading>

<Text>Start building your todo list now!</Text>

</VStack>

);

};

 

export default Home;

In the index.tsx file, we import and use Chakra UI components to create a simple welcome message for our app.

  1. Creating the User Interface with Chakra UI

Now that we have a basic structure for our frontend application, let’s create the user interface for our real-time todo app using Chakra UI components.

Create a new folder named components inside the client folder. Inside the components folder, create a new file named TodoList.tsx. This file will contain the UI components for displaying and managing the todo list.

import { VStack, Heading, Text, Button } from ‘@chakra-ui/react’;

 

const TodoList = () => {

return (

<VStack spacing={4} align=”center”>

<Heading size=”lg”>My Todo List</Heading>

<Text>List of todos goes here…</Text>

<Button colorScheme=”teal”>Add Todo</Button>

</VStack>

);

};

 

export default TodoList;

In the TodoList.tsx file, we import and use Chakra UI components to create a heading, a text description, and a button. This will serve as the basic structure for displaying the todo list and adding new todos.

  1. Fetching Data from the Backend with tRPC and React Query

In order to fetch data from the backend and update our frontend in real-time, we’ll use tRPC and React Query. tRPC provides the API layer for our backend, while React Query provides the data fetching and caching capabilities on the frontend.

To get started, install the necessary dependencies by running the following command:

yarn add @trpc/client @trpc/react-query

Next, create a new file named trpc.ts inside the client folder. This file will contain the necessary code to initialize tRPC and set up the API client.

import { createReactQueryHooks } from ‘@trpc/react-query’;

import { createTRPCClient } from ‘@trpc/client’;

import { AppRouter } from ‘@/server/route/app.router’;

export const trpc = createTRPCClient<AppRouter>({

url: ‘/api/trpc’,

});

 

export const { useQuery, useMutation } = createReactQueryHooks<AppRouter>();

In the trpc.ts file, we initialize tRPC with the URL of our API server. We also create React Query hooks for querying and mutating data using tRPC.

With the API client set up, we can now fetch data from the backend and update our frontend in real-time.

  1. Implementing Real-Time Updates with tRPC and WebSockets

To enable real-time updates in our todo app, we’ll use tRPC’s built-in support for WebSockets. WebSockets provide a persistent connection between the client and server, allowing for real-time communication.

To implement real-time updates, we need to make a few changes to our backend and frontend code.

In the server folder, install the necessary dependencies for WebSocket support:

yarn add @trpc/server@^8 ws

In the trpc.ts file, update the code as follows:

import { WebSocketLink } from ‘@trpc/server/adapters/websockets’;

const t = initTRPC.context<Context>().create({

transformer: superjson,

subscriptions: {

publish(ctx) {

return [

new WebSocketLink({

server: ctx.req.socket.server,

path: ‘/api/trpc’,

}),

];

},

},

});

In the trpc.ts file, we added the WebSocketLink from @trpc/server/adapters/websockets and configured it to use the WebSocket protocol. We also defined the subscriptions object, which will handle real-time updates.

Next, create a new file named useTodos.ts inside the client folder. This file will contain a custom hook for fetching and managing the todo list data.

import { useQuery } from ‘./trpc’;

import { AppRouter } from ‘@/server/route/app.router’;

export const useTodos = () => {

const { data: todos, isLoading, isError } = useQuery([‘todos.getAll’], async (query) => {

const result = await query.typed<AppRouter[‘todo’][‘getAll’]>({ type: ‘todo.getAll’ });

return result;

});

return { todos, isLoading, isError };

};

In the useTodos.ts file, we use the useQuery hook from React Query to fetch the list of todos from the backend using tRPC.

Finally, update the TodoList.tsx component to use the useTodos hook and display the list of todos:

import { VStack, Heading, Text, Button, Spinner, Alert, AlertIcon } from ‘@chakra-ui/react’;

import { useTodos } from ‘../useTodos’;

const TodoList = () => {

const { todos, isLoading, isError } = useTodos();

if (isLoading) {

return <Spinner />;

}

 

if (isError) {

return (

<Alert status=”error”>

<AlertIcon />

Error fetching todos. Please try again later.

</Alert>

);

}

return (

<VStack spacing={4} align=”center”>

<Heading size=”lg”>My Todo List</Heading>

{todos.map((todo) => (

<Text key={todo.id}>{todo.text}</Text>

))}

<Button colorScheme=”teal”>Add Todo</Button>

</VStack>

);

};

 

export default TodoList;

In the updated TodoList.tsx component, we check the loading and error states of the data fetching hook and display the appropriate UI components accordingly.

With the real-time updates implemented, our todo app is now capable of displaying and updating data in real-time.

 

Conclusion

In this comprehensive guide, we explored the process of building Real-Time Web applications using tRPC, Next.js, TypeScript, Chakra UI, and Prisma ORM. We started by setting up the backend with tRPC and Prisma ORM, defining the API routes, and implementing CRUD operations. Then, we moved on to building the frontend with Next.js and Chakra UI, fetching data from the backend using tRPC and React Query, and implementing real-time updates with tRPC and WebSockets.

By following this guide, you now have the knowledge and tools to build your own real-time web apps with tRPC. Whether you’re building a chat application, a collaborative document editor, or any other Real-Time Web applications, tRPC provides the foundation for creating fully type-safe APIs and enabling real-time communication between the client and server. Happy coding!

Visited 1 times, 1 visit(s) today

Leave a comment

Your email address will not be published. Required fields are marked *