How to Setup tRPC API with Prisma, PostgreSQL, Node & React
t3-stack popularly known astRPC is a lightweight library tailored specifically for building end-to-end typesafe APIs without the need for defined schemas or extra dependencies for code generation. This article will teach you how to useYarn Workspaces to set uptRPC with React.js, Express, Prisma, PostgreSQL, and Node.js.
At the time of writing this article,@tRPC/server now has adaptors for AWS Lambda, Express, and Fastify. Whereas the@tRPC/client can be used to create Vanilla, React.js, and Next.js clients.
In addition, the developer communities are working hard to create tRPC extensions for other frameworks, like Vue.js, Svelte, and many more.
tRPC API with React.js, Express, Prisma, Postgres, and Node.js Series:
- How to Setup tRPC API with Prisma, PostgreSQL, Node & React
- tRPC API with PostgreSQL, Prisma, Redis, & Node.js: JWT Authentication
Read more articles:
- Build tRPC API with React.js, Express & Node.js: Project Setup
- Golang & MongoDB: JWT Authentication and Authorization
- API with Golang + MongoDB: Send HTML Emails with Gomail
- API with Golang, Gin Gonic & MongoDB: Forget/Reset Password

Prerequisites
Before you begin, you should:
- HaveNode.js installed on your system
- Be comfortable with Node.js and Express
- Be comfortable with React.js
What is tRPC?
tRPC is a lightweight toolkit that allows developers to use the power of TypeScript to build end-to-end typesafe APIs with Express, Fastify, AWS Lambda, and more without the need to create schemas or install extra dependencies for code generation.
Whether you are new to this web development ecosystem or you are a veteran player, you may have noticed that GraphQL is the dominant library for building type-safety APIs using a query language.
GraphQL is amazing and solved most of our miseries, however, it doesn’t take full advantage of TypeScript to write type-safety APIs.
For this reason,tRPC was introduced to use the full power of TypeScript to build full-stack type-safety applications.
Setup PostgreSQL and Redis with Docker
PostgreSQL is a powerful, free, and open-source relational database management system used as the primary data store for web, and mobile applications.
Running PostgreSQL with Docker and docker-compose gives you the ability to easily run and maintain it, especially in a development environment.
To simply run the containers using the Postgres and Redis Docker images we can create adocker-compose.yml
file with the required configurations.
First, create a project folder namedtrpc-prisma-react
and open it with your preferred IDE or text editor.
$ mkdir trpc-prisma-react
Next, create adocker-compose.yml
in the project directory and add the following configurations to set up the Redis and PostgreSQL containers.
docker-compose.yml
version: '3'services: postgres: image: postgres container_name: postgres ports: - '6500:5432' restart: always env_file: - ./.env volumes: - postgres-db:/var/lib/postgresql/data redis: image: redis:latest container_name: redis ports: - '6379:6379' volumes: - redis:/datavolumes: postgres-db: redis:
Create a.env
file and add the following environment variables to send in the Postgres user, database name, and password.
.env
DATABASE_PORT=6500POSTGRES_PASSWORD=password123POSTGRES_USER=postgresPOSTGRES_DB=trpc_prismaPOSTGRES_HOST=postgresPOSTGRES_HOSTNAME=127.0.0.1
After this quick setup, start the containers with this command:
docker-compose up -d
Execute this command to stop the containers:
docker-compose down
Use Yarn Workspaces to Setup Monolithic Repository
First, let’s build a monorepo for the tRPC project with Yarn. For those who are new to the web and mobile development ecosystem, Yarn is simply a package manager developed by the guys at Facebook. It has a tool called Yarn workspaces, for organizing a project codebase into a monolithic repository.
With the explanation above, create apackage.json
file in the root workspace:
touch package.json
Next, add the following code to the newly-created package.json
file:
{ "name": "trpc-node-react", "private": "true", "scripts": {}, "workspaces": [ "packages/*" ]}
Workspaces are not meant to be published so we need to set the private property totrue in thepackage.json file to avoid publishing the root workspace by mistake.
Next, we used wildcard (*) in the “workspaces” array for Yarn to recognize the folders in the “packages” directory as packages.
Don’t be anxious, we will create the “packages” folder in a moment.
Create a Script to Start the tRPC client and server
Now let’s install the concurrently
andwsrun
packages help us start thetRPC client and server in parallel.
Using this approach will save us a lot of time since we do not have to manually change directories in the terminal to run the start scripts of the tRPC client and server.
Install theconcurrently
andwsrun
packages with this command:
yarn add -W -D concurrently wsrun
The “-W” flag tells Yarn to add the dependencies to the workspace itself. Whereas the “-D” flag tells Yarn to add the packages as dev-dependencies.
Update the package.json file in the root workspace with the following:
package.json
{ "name": "trpc-prisma-react", "private": "true", "scripts": { "start": "concurrently \"wsrun --parallel start\"" }, "workspaces": [ "packages/*" ], "devDependencies": { "concurrently": "^7.3.0", "wsrun": "^5.2.4" }}
The start script in the above will help us execute the start scripts in the tRPC client and server in parallel.
Since we will be pushing the project to GitHub, create a.gitignore
file in the root workspace and exclude thenode_modules.
.gitignore
node_modules
Creating the tRPC Node.js Server in Express
We are now ready to set up thetRPC Express Node.js server.
First, create a “server” folder in the “packages” folder then open the integrated terminal and change the directory into itcd packages/server
.
cd packages/server
Now run the command below to initialize a Node.js TypeScript project with Yarn:
yarn init -y && yarn add -D typescript && npx tsc --init
Next, open the newly-created “tsconfig.json” file and replace its content with the following configurations:
packages/server/tsconfig.json
{ "compilerOptions": { "target": "es2018", "experimentalDecorators": true, "emitDecoratorMetadata": true, "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "strictPropertyInitialization": false, "skipLibCheck": true }}
experimentalDecorators
– allows you to setexperimental support for decorators.emitDecoratorMetadata
– allows you to enable experimental support for emitting type metadata for the decorators.strictPropertyInitialization
– needed to prevent Typescript from raising errors when we declare the Typegoose class attributes without initializing them.
Add the dependencies required to set up thetRPC server with Express:
yarn add @trpc/server@next cors dotenv express @prisma/client redis && yarn add -D prisma @types/cors @types/express @types/node morgan @types/morgan ts-node-dev
@trpc/server
– has tools for building thetRPC endpoints and routerscors
– needed to configure thetRPC Express server to accept requests from cross-origin domains.dotenv
– loads the environment variables from a configuration file into the Node.js environment.express
– a lightweight Node.js framework for building web and mobile applications.@prisma/client
– the query builder for Prismaprisma
– the CLI for running Prisma commandsredis
– a JavaScript library for accessing and mutating the Redis databasets-node-dev
– to hot-reload thetRPC Express server upon every file change.morgan
– an HTTP logger middleware
The security of thetRPC application is very important and it’s always a best practice to store sensitive data in an environment variables file. Create apackages/server/.env
file and add the following credentials:
NODE_ENV=developmentORIGIN=http://127.0.0.1:3000DATABASE_URL="postgresql://postgres:password123@localhost:6500/trpc_prisma?schema=public"
Open thepackages/server/package.json
file and add the start script to help us start the tRPC Express server.
packages/server/package.json
{"scripts": { "start": "ts-node-dev --respawn --transpile-only src/app.ts" }}
Within thepackages/server
folder, create an “src” folder. Next, create two folders named “config” and “utils” in the “src” directory.
Next, create adefault.ts
file in the “config” directory to enable us to load and export the environment variables.
packages/server/src/config/default.ts
import path from 'path';require('dotenv').config({ path: path.join(__dirname, '../../.env') });const customConfig: { port: number; origin: string; dbUri: string } = { port: 8000, origin: process.env.ORIGIN as unknown as string, dbUri: process.env.DATABASE_URL as unknown as string,};export default customConfig;
Connect the tRPC Server to PostgreSQL and Redis
Now that we have the Redis and PostgreSQL servers running in the Docker containers, let’s define some utility functions to connect them to thetRPC server.
packages/server/src/utils/prisma.ts
import { PrismaClient } from '@prisma/client';declare global { var prisma: PrismaClient | undefined;}export const prisma = global.prisma || new PrismaClient({ log: ['query', 'info'] });if (process.env.NODE_ENV !== 'production') { global.prisma = prisma;}async function connectDB() { try { await prisma.$connect(); console.log('? Database connected successfully'); } catch (error) { console.log(error); process.exit(1); } finally { await prisma.$disconnect(); }}export default connectDB;
packages/server/src/utils/connectRedis.ts
import { createClient } from 'redis';const redisUrl = `redis://localhost:6379`;const redisClient = createClient({ url: redisUrl,});const connectRedis = async () => { try { await redisClient.connect(); console.log('? Redis client connected...'); redisClient.set( 'tRPC', '??Welcome to tRPC with React.js, Express and Typescript!' ); } catch (err: any) { console.log(err.message); process.exit(1); }};connectRedis();redisClient.on('error', (err) => console.log(err));export default redisClient;
In the above, we created a function to connect to the Redis server and evoked theredisClient.set()
function returned by calling thecreateClient()
function to add a message to the Redis database with a “tRPC” key.
Later, we will request the message from the Redis database and send it to thetRPC React.js client.
Starting the tRPC Express Server
Creating the tRPC Context
Now let’s create a context function that will be called for every incoming request and the results will be passed as contextual data to all the resolvers.
packages/server/src/app.ts
import path from 'path';import dotenv from 'dotenv';import express from 'express';import morgan from 'morgan';import cors from 'cors';import { inferAsyncReturnType, initTRPC, TRPCError } from "@trpc/server";import * as trpcExpress from '@trpc/server/adapters/express';import redisClient from './utils/connectRedis';import customConfig from './config/default';import connectDB from './utils/prisma';dotenv.config({ path: path.join(__dirname, './.env') });const createContext = ({ req, res,}: trpcExpress.CreateExpressContextOptions) => ({ req, res });export type Context = inferAsyncReturnType<typeof createContext>;// [...]
Creating the tRPC Router
Next, let’s use the “router()” function provided bytRPC to create a router to manage thetRPC endpoints. This will allow us to add:
- Query endpoints – for requesting data
- Mutation endpoints – to perform theCreate,Update,andDelete operations.
- Subscription endpoints – used to subscribe to data over WebSockets.
packages/server/src/app.ts
// [...]const t = initTRPC.context<Context>().create();const appRouter = t.router({ sayHello: t.procedure.query(async () => { const message = await redisClient.get("tRPC"); return { message }; }),});export type AppRouter = typeof appRouter;
Let’s evaluate the code above:
First, we initialized the tRPC server by evoking the.context<Context>()
and.create()
methods. Then, we created the tRPC router and added asayHello procedure call. The sayHello query will be evoked by the tRPC client to return the message stored in the Redis database.
TheappRouter
returned by evokingt.router()
function maintains all the endpoints, including what the routes accept as inputs, and their outputs.
Next, we exported theAppRouter type from thepackages/server/src/app.ts
file. This will allow us to install thetRPC server as a package in the tRPC client in order for the React client to know the different queries, mutations, and subscriptions available on thetRPC server.
Configuring the Express tRPC Server
packages/server/src/app.ts
// [...]const app = express();if (process.env.NODE_ENV !== "production") app.use(morgan("dev"));app.use( cors({ origin: [customConfig.origin, "http://localhost:3000"], credentials: true, }));app.use( "/api/trpc", trpcExpress.createExpressMiddleware({ router: appRouter, createContext, }));const port = customConfig.port;app.listen(port, () => { console.log(`🚀 Server listening on port ${port}`); // CONNECT DB connectDB();});
Complete Express tRPC Server Code
packages/server/src/app.ts
import path from "path";import dotenv from "dotenv";import express from "express";import morgan from "morgan";import cors from "cors";import { inferAsyncReturnType, initTRPC } from "@trpc/server";import * as trpcExpress from "@trpc/server/adapters/express";import redisClient from "./utils/connectRedis";import customConfig from "./config/default";import connectDB from "./utils/prisma";dotenv.config({ path: path.join(__dirname, "./.env") });const createContext = ({ req, res,}: trpcExpress.CreateExpressContextOptions) => ({ req, res });export type Context = inferAsyncReturnType<typeof createContext>;const t = initTRPC.context<Context>().create();const appRouter = t.router({ sayHello: t.procedure.query(async () => { const message = await redisClient.get("tRPC"); return { message }; }),});export type AppRouter = typeof appRouter;const app = express();if (process.env.NODE_ENV !== "production") app.use(morgan("dev"));app.use( cors({ origin: [customConfig.origin, "http://localhost:3000"], credentials: true, }));app.use( "/api/trpc", trpcExpress.createExpressMiddleware({ router: appRouter, createContext, }));const port = customConfig.port;app.listen(port, () => { console.log(`🚀 Server listening on port ${port}`); // CONNECT DB connectDB();});
Turning the tRPC Express Server into a Library
To convert the tRPC server into a library, we need to add"main": "src/app.ts"
to thepackages/server/package.json
file. This will allow us to have access to theAppRouter type when we install thetRPC server in thetRPC React.js client.
packages/server/package.json

packages/server/package.json
{ "name": "server", "version": "1.0.0", "main": "src/app.ts", "license": "MIT", "scripts": { "start": "ts-node-dev --respawn --transpile-only src/app.ts", "db:migrate": "npx prisma migrate dev --name user-entity --create-only && yarn prisma generate", "db:push": "npx prisma db push" }, "devDependencies": { "@types/cors": "^2.8.12", "@types/express": "^4.17.14", "@types/morgan": "^1.9.3", "morgan": "^1.10.0", "prisma": "^4.5.0", "ts-node-dev": "^2.0.0", "typescript": "^4.8.4" }, "dependencies": { "@prisma/client": "^4.5.0", "@trpc/server": "^10.0.0-proxy-beta.26", "cors": "^2.8.5", "dotenv": "^16.0.3", "express": "^4.18.2", "redis": "^4.3.1" }}
Database Migration with Prisma
There are other ORMs for Node.js but we are going to use Prisma to query and mutate the database.
To get started, run the following code to initialize a new Prisma project in thepackages/server
folder.
Change the directory into thepackages/server
folder and run this command:
npx prisma init
The above command will generate a newpackages/server/prisma
folder containing aschema.prisma
file. Also, when you open the environment variables file in the server folder, you should see aDATABASE_URL
variable added by the Prisma CLI.
Now replace the content of thepackages/server/prisma/schema.prisma
file with the following code:
packages/server/prisma/schema.prisma
generator client { provider = "prisma-client-js"}datasource db { provider = "postgresql" url = env("DATABASE_URL")}model User{ @@map(name: "users") id String @id @default(uuid()) name String @db.VarChar(255) email String @unique photo String? @default("default.png") verified Boolean? @default(false) password String role RoleEnumType? @default(user) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt provider String?}enum RoleEnumType { user admin}
Quite a lot going on in the above, let’s break it down:
- First, we created a
User
model and used the@@map(name: "users")
attribute to change the table name tousers
. - Then we defined the attributes needed to create a new user in the database.
- The
@default(uuid())
attribute sets a defaultUUID value for the ID field in the database.
With the above explanation, add the following scripts to thepackages/server/package.json
file.
{"scripts": { "start": "ts-node-dev --respawn --transpile-only src/app.ts", "db:migrate": "npx prisma migrate dev --name user-entity --create-only && yarn prisma generate", "db:push": "npx prisma db push" },}
db:migrate
– create a new Prisma migration file and generate the TypeScript types based on the defined model.db:push
– push the changes to the PostgreSQL database and ensures that the database is in-sync with the schema.
Add the PostgreSQL credentials defined in the.env
file to theDATABASE_URL
added by the Prisma CLI.
DATABASE_URL="postgresql://postgres:password123@localhost:6500/trpc_prisma?schema=public"
Now to create theusers
table in the PostgreSQL database, run the following command:
yarn db:migrate && yarn db:push
When you log into the PostgreSQL database in pgAdmin with the credentials defined in the.env
, you should see theusers
table with the following properties.

Creating the tRPC Client in React.js
First, open the integrated terminal and change the directory into the “packages” folder to enable us to create a boilerplate React.js app with theVite scaffolding tool.
Run this command to scaffold a new Vite project with Yarn:
yarn create vite
The above command will install and execute the Vite executable from the remote npm
repository and you will be presented with prompts for a number of features like:
- The project name
- The type of framework
- TypeScript support
After the script has been executed, the tool will prompt you to enter a project name:
$ yarn create viteyarn create v1.22.19[1/4] Resolving packages...[2/4] Fetching packages...[3/4] Linking dependencies...[4/4] Building fresh packages...success Installed "create-vite@3.0.0" with binaries: - create-vite - cva? Project name: » client
Type “client” as the project name and press the Enter key
After pressing the Enter key, you will be prompted to select a framework
? Select a framework: » - Use arrow-keys. Return to submit. vanilla vue> react preact lit svelte
After using the arrow keys to select the framework, press the Enter key again and Vite will prompt you to choose the language type.
? Select a variant: » - Use arrow-keys. Return to submit. react> react-ts
Selectreact-ts
from the list and press the Enter key. Vite will then generate the project and instruct you to install the dependencies.
Scaffolding project in C:\Users\zidda\Desktop\Learn\Blog\tRPC\trpc-prisma-react\testing\client...Done. Now run: cd client yarn yarn devDone in 544.96s.
Change the directory into the project folder as directed and runyarn
to install the dependencies.
Next, open thepackages/client/package.json
file and change the dev script to start. Also, add the port flag to start the dev server on port3000.
packages/client/package.json
{"scripts": { "start": "vite --port 3000", "build": "tsc && vite build", "preview": "vite preview" }}
We are now ready to install thetRPC server as a library in the React client. In your terminal, ensure that you are in the client folder and run the command below to install thetRPC server library.
yarn add server@1.0.0
server
– is the property name in thepackages/server/package.json
file
@1.0.0
– is the corresponding version specified in it.
Next, run the following command to install the dependencies needed to set up thetRPC client:
yarn add @trpc/client@next @trpc/server@next @trpc/react-query@next @tanstack/react-query @tanstack/react-query-devtools
@tanstack/react-query
– Library for managing server state in React applications.@trpc/react-query
– A wrapper around React Query@trpc/client
– has tools for creating thetRPC client@trpc/server
– This is a peer dependency of@trpc/client
so you have to install it again!@tanstack/react-query-devtools
– A tool for visualizing all the inner workings of React Query.
Creating the tRPC Client
Now let’s use thecreateTRPCReact()
function provided by@trpc/react-query
binding to generate the React Query hooks based on the queries, mutations, and subscriptions defined on thetRPC server.
packages/client/src/utils/trpc.ts
import { createTRPCReact } from "@trpc/react-query";import type { AppRouter } from "server";export const trpc = createTRPCReact<AppRouter>();
Testing the tRPC API Endpoint
Now let’s configure the React.js client withtRPC andReact Query before making our firsttRPC request to our API server.
packages/client/src/App.tsx
import "./global.css";import { useState } from "react";import { QueryClientProvider, QueryClient } from "@tanstack/react-query";import { ReactQueryDevtools } from "@tanstack/react-query-devtools";import { getFetch } from "@trpc/client";import { loggerLink } from "@trpc/client/links/loggerLink";import { httpBatchLink } from "@trpc/client/links/httpBatchLink";import { trpc } from "./utils/trpc";function AppContent() { const hello = trpc.sayHello.useQuery(); return <main className="p-2">{JSON.stringify(hello.data, null, 2)}</main>;}function App() { const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 1000, }, }, }) ); const [trpcClient] = useState(() => trpc.createClient({ links: [ loggerLink(), httpBatchLink({ url: "http://localhost:8000/api/trpc", fetch: async (input, init?) => { const fetch = getFetch(); return fetch(input, { ...init, credentials: "include", }); }, }), ], }) ); return ( <trpc.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}> <AppContent /> <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> </trpc.Provider> );}export default App;
With the above configurations, we are now ready to start the tRPC client and server. Open your terminal and change the directory into the root Yarn workspace and runyarn start
to start both thetRPC client and server.
Enter thetRPC client URLhttp://127.0.0.1:3000/
in the browser and you should see the message we stored in the Redis database.

Adding tailwindCss to the React.js tRPC Client
In this section, follow the steps below to add tailwindCss to the React.js tRPC client.
Step 1: Install tailwindCss and its dependencies
Open the integrated terminal and change the directory to thepackages/client
folder. Run this command to install the tailwindCss library and its peer dependencies via Yarn.
yarn add -D tailwindcss postcss autoprefixer
Next, run the init command to generate thetailwind.config.js
andpostcss.config.js
configuration files.
npx tailwindcss init -p
Step 2: Configure Template Paths
Next, open the tailwind.config.js
file and add the path to the React template files. You can also include your custom colors and fonts.
packages/client/tailwind.config.js
/** @type {import('tailwindcss').Config} */module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: { colors: { 'ct-dark-600': '#222', 'ct-dark-200': '#e5e7eb', 'ct-dark-100': '#f5f6f7', 'ct-blue-600': '#2363eb', 'ct-yellow-600': '#f9d13e', }, container: { center: true, padding: '1rem', screens: { lg: '1125px', xl: '1125px', '2xl': '1125px', }, }, }, }, plugins: [],};
Step 3: Adding the tailwindCss directives
Now create a packages/client/src/global.css
file and add the TailwindCSS directives.
packages/client/src/global.css
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');@tailwind base;@tailwind components;@tailwind utilities;@layer base { html { font-family: 'Poppins', sans-serif; }}
Step 2: Importing the CSS file
We are now ready to start using the tailwindCss classes but before that let’s import thepackages/client/src/global.css
file into theApp.tsx
file.
packages/client/src/App.tsx
import './global.css';// [...] other imports
Next, replace the content of theAppContent()
component in thepackages/client/src/App.tsx
file with the following:
packages/client/src/App.tsx
function AppContent() { const hello = trpc.sayHello.useQuery(); return ( <div className="container mx-auto bg-ct-dark-200 rounded-xl shadow border p-8 m-10"> <p className="text-3xl text-gray-700 font-bold mb-5">Welcome!</p> <p className="text-ct-blue-600 text-lg">{hello.data?.message}</p> </div> );}
You should now see the following output in the browser:

Conclusion
With this tRPC client and server example, you’ve learned how to set up tRPC with React.js, PostgreSQL, Prisma, Express, and Redis.
You can find the source code of the tRPC client and serveron my GitHub
CRUD RESTful API Server with Python, FastAPI, and MongoDB
tRPC API with Postgres, Prisma & Node.js: JWT Authentication
Leave a ReplyCancel reply
This site uses Akismet to reduce spam.Learn how your comment data is processed.
Support Me!

Recent posts
Categories
- C#(2)
- C++(1)
- CSS / SCSS(3)
- Deno(8)
- Golang(31)
- JavaScript(5)
- NextJs(38)
- NodeJS(32)
- Programming(19)
- Python(19)
- React(38)
- Rust(35)
- Svelte(5)
- Vue(7)