Movatterモバイル変換


[0]ホーム

URL:


CodevoWeb

PressESC to close

Build tRPC API with React.js, Express & Node.js: Project Setup

2Comments37

tRPC akat3-stack is a light library for building end-to-end typesafe APIs for Next.js and Node apps without writing schemas or installing libraries for code generation. This article will teach you how to set up tRPC with React.js, Express, and Node.js usingYarn Workspaces.

tRPC API with React.js, Express, and Node.js Series:

  1. Build tRPC API with React.js, Node.js & MongoDB: Project Setup
  2. Build tRPC API with React.js & Node.js: Access and Refresh Tokens
  3. Full-Stack App tRPC, React.js, & Node.js: JWT Authentication

Read more articles:

Build tRPC API with React.js, Node.js & MongoDB Project Setup

What is tRPC?

tRPC is a tool that leverages the full power of modern TypeScript to build end-to-end type-safety APIs with Node.js and Next.js without defining schemas. It allows us to share the Typescript types directly between the backend and the frontend without relying on code generation.

Currently, GraphQL is the go-to library for building type-safety APIs to solve some of the downsides of RESTful APIs. However, since GraphQL is a query language, it doesn’t take full advantage of TypeScript to implement type-safety APIs.

This is wheretRPC comes in, this tool uses the full power of Typescript to create a type-safe client and server by only using inference

Setup MongoDB and Redis with Docker-compose

MongoDB is an open-source NoSQL database management program that uses JSON-like documents calledBSON with optional schemas to store data.

The quickest way to run the MongoDB database server on a machine is to use a Docker container so am going to assume you already haveDocker installed on your machine.

By default, Docker-compose is also installed along with Docker to allow you to run multiple containers using adocker-compose.yaml file.

Create a new folder calledtrpc-node-react with your preferred local terminal and open the folder with your text editor of choice.

mkdir trpc-node-react

In the root directory, create adocker-compose.yml file and add the following Docker configurations:

docker-compose.yml

version: '3.8'services:  mongo:    image: mongo:latest    container_name: mongo    env_file:      - ./.env    environment:      MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}      MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}    volumes:      - mongo:/data/db    ports:      - '6000:27017'  redis:    image: redis:latest    container_name: redis    ports:      - '6379:6379'    volumes:      - redis:/datavolumes:  mongo:  redis:

The configurations above will allow us to run both the Redis and MongoDB servers on our machine.

Now create a.env file in the root project to contain the credentials required by the Mongo Docker image to configure the MongoDB server.

.env

MONGO_INITDB_ROOT_USERNAME=adminMONGO_INITDB_ROOT_PASSWORD=password123MONGO_INITDB_DATABASE=trpc_mongodb

With those configurations in place, run this command to start the Redis and MongoDB Docker containers:

docker-compose up -d

Run this command to stop the containers:

docker-compose down

Setup a Monolithic Repository with Yarn Workspaces

To begin, let’s build a monorepo using Yarn. Yarn is a package manager developed and maintained by the folks at Facebook and it comes with a tool called Yarn workspaces for organizing a project codebase using a monolithic repository aka monorepo.

Create apackage.json file in the root workspace:

touch package.json

Add the following code to the newly-created package.json file:

{  "name": "trpc-node-react",  "private": "true",  "scripts": {},  "workspaces": [    "packages/*"  ]}

Since workspaces are not meant to be published, you need to make the package.json private to avoid accidentally publishing the root workspace.

Also, we used a wildcard (*) in the “workspaces” property array to tell Yarn to include all the packages inside the “packages” folder.

Add a script to run both the tRPC client and server

Now we need a way to run both packages — the tRPC React client and the Node.js client simultaneously. For this example, we will use the concurrently andwsrun packages to run the start scripts of both packages in parallel.

Addconcurrently andwsrun to the rootpackage.json:

yarn add -W -D concurrently wsrun

Add this script to the root workspace package.json to run the start scripts of the tRPC client and server in parallel.

package.json

{  "name": "trpc-node-react",  "private": "true",  "scripts": {    "start": "concurrently \"wsrun --parallel start\""  },  "workspaces": [    "packages/*"  ],  "devDependencies": {    "concurrently": "^7.2.2",    "wsrun": "^5.2.4"  }}

Create a.gitignore file in the root workspace and add thenode_modules to exclude them from your commits.

.gitignore

node_modules

Creating the Node.js tRPC Express Server

Now it’s time to set up thetRPC Express Node.js server. Create a folder named “server” inside the “packages” folder.

Open the built-in terminal in your text editor and change the directory to thepackages/server folder:

cd packages/server

Run this command to initialize a Node.js Typescript project with Yarn:

yarn init -y && yarn add -D typescript && npx tsc --init

Now replace the content of the newly-created “tsconfig.json” file 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 – If set to “true“,experimental support for decorators will be enabled.
  • emitDecoratorMetadata – If set to “true” experimental support for emitting type metadata for the decorators will be enabled.
  • strictPropertyInitialization – If set to “false“, Typescript won’t raise errors when we declare the Typegoose class properties without initializing them in a constructor.

Install the dependencies needed to set up thetRPC server with ExpressJs:

yarn add @trpc/server@next cors dotenv express mongoose redis && yarn add -D @types/express @types/node @types/cors morgan @types/morgan ts-node-dev
  • @trpc/server – for implementingtRPC endpoints and routers
  • cors – to allow thetRPC server to accept requests from cross-origin domains.
  • dotenv – to load the environment variables into the Node.js environment.
  • express – a lightweight Node.js web application framework for building web and mobile applications.
  • mongoose – a library that uses schemas to define MongoDB models.
  • redis – a Node.js library for interacting with a Redis server
  • ts-node-dev – to hot-reload thetRPC Express server.
  • morgan – for logging HTTP requests in the terminal

Since the security of thetRPC application is really important to us, create a.env file in thepackages/server folder and add the following data.

NODE_ENV=developmentORIGIN=http://localhost:3000MONGODB_URI=mongodb://admin:password123@localhost:6000/trpc_mongodb?authSource=admin

Add the start script to thepackages/server/package.json file to initialize thetRPC Express server.

packages/server/package.json

{"scripts": {    "start": "ts-node-dev --respawn --transpile-only src/app.ts"  }}

In thepackages/server folder, create an “src” folder, and within the “src” folder create a “config” and “utils” folders.

Now createdefault.ts file in the “config” folder to import and export the environment variables we stored in thepackages/server/.env file.

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.MONGODB_URI as unknown as string,};export default customConfig;

Connect the tRPC Server to Redis and MongoDB

Now that we have the Redis and MongoDB servers running in the Docker containers, let’s create some helper functions to connect them to thetRPC application.

packages/server/src/utils/connectDB.ts

import mongoose from 'mongoose';import customConfig from '../config/default';const dbUrl = customConfig.dbUri;const connectDB = async () => {  try {    await mongoose.connect(dbUrl);    console.log('? Database connected...');  } catch (error: any) {    console.log(error);    process.exit(1);  }};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 rRPC 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 code, we evoked theredisClient.set() method available on the Redis client instance to add a message to the Redis database with a “tRPC” key.

Later, we will retrieve that message from the Redis database and return it to the “tRPC” client.

Initialize the tRPC Express Server

Create the tRPC Context

Let’s create an app context that will be generated for every incoming request and the results will be propagated to all the resolvers. This will enable us to pass down contextual data to our 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 * as trpcExpress from "@trpc/server/adapters/express";import connectDB from "./utils/connectDB";import redisClient from "./utils/connectRedis";import customConfig from "./config/default";import { inferAsyncReturnType, initTRPC } from "@trpc/server";dotenv.config({ path: path.join(__dirname, "./.env") });const createContext = ({  req,  res,}: trpcExpress.CreateExpressContextOptions) => ({ req, res });export type Context = inferAsyncReturnType<typeof createContext>;// [...]

Create the tRPC Router

Now let’s create a router to enable us to manage thetRPC endpoints. With the router defined, we can add:

  • Query routes – for fetching data
  • Mutation routes – for modifying data (Create,UpdateandDelete data)
  • Subscription routes – allows us 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;

Here we initialized the tRPC server by calling the.context<Context>() and.create() methods oninitTRPC . Then, we create a new tRPC router and added asayHello query procedure call. ThesayHello procedure will be evoked by the tRPC client to return the message we stored in the Redis database.

Lastly, we exposed theAppRouter typescript type to enable the React client to know the different queries, mutations, and subscriptions available on thetRPC server.

AppRouter maintains all the routes, what the routes take as inputs, and what the routes return as outputs.

Start 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

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 * as trpcExpress from "@trpc/server/adapters/express";import connectDB from "./utils/connectDB";import redisClient from "./utils/connectRedis";import customConfig from "./config/default";import { inferAsyncReturnType, initTRPC } from "@trpc/server";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();});

Turn the tRPC Express App into a Library

Add a"main": "src/app.ts" to thepackages/server/package.json to turn thetRPC server into a library. This will allow us to connect both thetRPC React.js client to the Node.js server.

packages/server/package.json

turn tRPC server to a library

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"  },  "dependencies": {    "@trpc/server": "^10.0.0-proxy-beta.26",    "cors": "^2.8.5",    "dotenv": "^16.0.3",    "express": "^4.18.2",    "mongoose": "^6.7.0",    "redis": "^4.3.1"  },  "devDependencies": {    "@types/cors": "^2.8.12",    "@types/morgan": "^1.9.3",    "morgan": "^1.10.0",    "ts-node-dev": "^2.0.0",    "typescript": "^4.8.4"  }}

Creating the React.js tRPC Client

First and foremost, change the directorycd into the “packages” folder to enable us to generate a basic React.js app with “create-react-app“.

Run this command to create a React.js boilerplate app in apackages/client folder.

yarn create react-app client --template typescript

Replace the content of thepackages/client/tsconfig.json file with the following:

{  "compilerOptions": {    "target": "es5",    "lib": ["dom", "dom.iterable", "esnext"],    "allowJs": true,    "skipLibCheck": true,    "esModuleInterop": true,    "allowSyntheticDefaultImports": true,    "strict": true,    "forceConsistentCasingInFileNames": true,    "noFallthroughCasesInSwitch": true,    "module": "esnext",    "moduleResolution": "node",    "resolveJsonModule": true,    "isolatedModules": true,    "noEmit": true,    "jsx": "react-jsx"  },  "include": ["src"]}

Now let’s connect thetRPC server to the React client by installing the API server as a library on the client.

yarn add server@1.0.0

Where “server” is the property name in thepackages/server/package.json file and the “@1.0.0” is the version provided in it. This will enable the React.js app to import theAppRouter type we exported from thetRPC server.

Next, install the dependencies required to set up thetRPC Client

yarn add @trpc/client@next @trpc/server@next @trpc/react-query@next @tanstack/react-query
  • react-query – a library for managing server state
  • @trpc/react – is a thin wrapper around React Query
  • @trpc/client – for creating thetRPC client
  • @trpc/server – It’s a peer dependency of@trpc/client .

Create the tRPC Client

Now let’s create the React Query hooks that are specific to our API server with thecreateTRPCReact() provided by@trpc/react-query binding.

packages/client/src/trpc.ts

import { createTRPCReact } from "@trpc/react-query";import type { AppRouter } from "server";export const trpc = createTRPCReact<AppRouter>();

Calling our 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 { useState } from "react";import { QueryClientProvider, QueryClient } from "@tanstack/react-query";import { ReactQueryDevtools } from "@tanstack/react-query-devtools";import { getFetch, httpBatchLink, loggerLink } from "@trpc/client";import { trpc } from "./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;

Change directory into the root Yarn workspace and runyarn start to start thetRPC client and server.

You should see the message we stored in the Redis database in the browser.

tRPC server and client Redis message

Setup tailwindCss with the React.js tRPC Client

Install tailwindCss and its dependencies

Now change the directory to thepackages/client folder and install tailwindCss and its peer dependencies via Yarn.

yarn add -D tailwindcss postcss autoprefixer

Run the init command to generate thetailwind.config.js andpostcss.config.js files in thepackages/client folder.

npx tailwindcss init -p

Add the Template Paths to the Configuration File

Next, add the paths to the template files in the tailwind.config.js file, and also, remember to 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: [],};

Add the tailwindCss directives to your CSS

Create a ./packages/client/src/global.css file and add the @tailwind directives. You also need to include your custom font to override the default font that comes with TailwindCSS.

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;  }}

Import the CSS file

Now, import the newly-created packages/client/src/global.css file into the packages/client/src/index.tsx file.

packages/client/src/index.tsx

import React from 'react';import ReactDOM from 'react-dom/client';import App from './App';import './global.css';const root = ReactDOM.createRoot(  document.getElementById('root') as HTMLElement);root.render(  <react.strictmode>    <app>  </react.strictmode>);

Conclusion

Congrats on reaching the end. In this comprehensive article, you’ve learned how to set uptRPC with React.js, Node.js, Redis, and MongoDB.

tRPC Client and Server Project Setup Source Code

You can find the completesource code on GitHub

Share Article:

Golang, SQLC, and PostgreSQL: JWT Access & Refresh Tokens

Left Arrow

Build tRPC API with React.js & Node.js: Access and Refresh Tokens

Right Arrow

2 Comments

  1. Sebastianon January 5, 2023

    Verry, verry nice article. Thanks 🙂

    Reply
  2. Mannyon January 24, 2023

    How would one deploy such a config to say, aws for express backend and vercel for react front end?

    Reply

Leave a ReplyCancel reply

This site is protected by reCAPTCHA and the GooglePrivacy Policy andTerms of Service apply.

This site uses Akismet to reduce spam.Learn how your comment data is processed.

Support Me!

paypal donate button

Recent posts

Categories


[8]ページ先頭

©2009-2025 Movatter.jp