{"id":4263,"date":"2022-07-04T15:30:54","date_gmt":"2022-07-04T15:30:54","guid":{"rendered":"https:\/\/codevoweb.com\/?p=4263"},"modified":"2023-05-06T08:28:27","modified_gmt":"2023-05-06T08:28:27","slug":"trpc-api-with-reactjs-nodejs-access-and-refresh-tokens","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/trpc-api-with-reactjs-nodejs-access-and-refresh-tokens\/","title":{"rendered":"Build tRPC API with React.js &#038; Node.js: Access and Refresh Tokens"},"content":{"rendered":"\n<p>When we build a full-stack TypeScript application, it becomes challenging to share types directly between the client and server, without depending on code generation or schemas defined on the server.<\/p>\n\n\n\n<p>This is where GraphQL was developed to offer a radically new way to build type-safety APIs with more flexibility and control.<\/p>\n\n\n\n<p>But the flexibility that GraphQL brought came at a price in the form of extra setup and complexity. For this reason, crop frameworks, libraries, and services were introduced to help smoothen the workflows and provide better patterns to reduce the complexities.<\/p>\n\n\n\n<p>With all the advantages GraphQL has over RESTful API, it doesn&#8217;t take full advantage of TypeScript to build type-safety APIs.<\/p>\n\n\n\n<p>In this article, you&#8217;ll learn how to add access and refresh token functionality to your <a href=\"https:\/\/trpc.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">tRPC<\/a> API using Node.js, Express, MongoDB, Redis, and Docker.<\/p>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-107\"><\/span>\n\n\n\n<p><strong>tRPC<\/strong> API with React.js, Express, and Node.js Series:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"\/trpc-api-reactjs-nodejs-mongodb-project-setup\">Build tRPC API with React.js, Node.js &amp; MongoDB: Project Setup<\/a><\/li>\n\n\n\n<li><a href=\"\/trpc-api-with-reactjs-nodejs-access-and-refresh-tokens\">Build tRPC API with React.js &amp; Node.js: Access and Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/fullstack-app-trpc-reactjs-nodejs-jwt-authentication\">Full-Stack App tRPC, React.js, &amp; Node.js: JWT Authentication<\/a><\/li>\n<\/ol>\n\n\n\n<p>Related Articles:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/golang-mongodb-jwt-authentication-authorization\">Golang &amp; MongoDB: JWT Authentication and Authorization<\/a><\/li>\n\n\n\n<li><a href=\"\/api-golang-mongodb-send-html-emails-gomail\">API with Golang + MongoDB: Send HTML Emails with Gomail<\/a><\/li>\n\n\n\n<li><a href=\"\/api-golang-gin-gonic-mongodb-forget-reset-password\">API with Golang, Gin Gonic &amp; MongoDB: Forget\/Reset Password<\/a><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"850\" height=\"478\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-tRPC-API-with-React.js-MongoDB-Redis-Docker-Node.js-Access-and-Refresh-Tokens.webp\" alt=\"Build tRPC API with React.js, MongoDB, Redis, Docker &amp; Node.js Access and Refresh Tokens\" class=\"wp-image-4344\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-tRPC-API-with-React.js-MongoDB-Redis-Docker-Node.js-Access-and-Refresh-Tokens.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-tRPC-API-with-React.js-MongoDB-Redis-Docker-Node.js-Access-and-Refresh-Tokens-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-tRPC-API-with-React.js-MongoDB-Redis-Docker-Node.js-Access-and-Refresh-Tokens-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-tRPC-API-with-React.js-MongoDB-Redis-Docker-Node.js-Access-and-Refresh-Tokens-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-tRPC-API-with-React.js-MongoDB-Redis-Docker-Node.js-Access-and-Refresh-Tokens-700x394.webp 700w\" sizes=\"auto, (max-width: 850px) 100vw, 850px\" \/><\/figure>\n\n\n<style>.kb-table-of-content-nav.kb-table-of-content-id_b626b9-81 .kb-table-of-content-wrap{padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}.kb-table-of-content-nav.kb-table-of-content-id_b626b9-81 .kb-table-of-contents-title-wrap{padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px;}.kb-table-of-content-nav.kb-table-of-content-id_b626b9-81 .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_b626b9-81 .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_b626b9-81 .kb-table-of-content-wrap .kb-table-of-content-list{color:#ffffff;font-weight:regular;font-style:normal;margin-top:10px;margin-right:0px;margin-bottom:0px;margin-left:-5px;}@media all and (max-width: 1024px){.kb-table-of-content-nav.kb-table-of-content-id_b626b9-81 .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}@media all and (max-width: 767px){.kb-table-of-content-nav.kb-table-of-content-id_b626b9-81 .kb-table-of-content-wrap{border-top:1px solid #abb8c3;border-right:1px solid #abb8c3;border-bottom:1px solid #abb8c3;border-left:1px solid #abb8c3;}}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>Due to the flaws of GraphQL, <a href=\"https:\/\/trpc.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">tRPC<\/a> was introduced to provide better ways to statically type our APIs and share those types between the frontend and backend. <\/p>\n\n\n\n<p><a href=\"https:\/\/trpc.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">tRPC<\/a> can be considered as a wrapper around RESTful APIs but it provides a simpler pattern for building type-safety APIs with TypeScript and couples the client and server more tightly together.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before we begin, you should:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Have <a href=\"https:\/\/nodejs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Node installed<\/a> on your system<\/li>\n\n\n\n<li>Have basic knowledge of JavaScript and TypeScript<\/li>\n\n\n\n<li>Have <a href=\"https:\/\/docs.docker.com\/get-docker\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker installed<\/a> on your system<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Project Setup<\/h2>\n\n\n\n<p>We are going to use Express and <strong>tRPC<\/strong> along with the Express adapter to create the <strong>tRPC<\/strong> endpoints.<\/p>\n\n\n\n<p>Also, we will use MongoDB as our database and Redis to store the authenticated user&#8217;s session. Adding Redis to the authentication flow will add an extra layer of security to our application.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Follow the <a href=\"\/trpc-api-reactjs-nodejs-mongodb-project-setup\">Project Setup<\/a> article to set up the <strong>tRPC<\/strong> project with Express before continuing with this article.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Create the User Model with Typegoose<\/h2>\n\n\n\n<p>Before we start defining the models, open your terminal and change the directory into the server folder.<\/p>\n\n\n\n<p>Run this command to install <code>typegoose<\/code> and <code>bcryptjs<\/code> :<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add @typegoose\/typegoose bcryptjs &amp;&amp; yarn add -D @types\/bcryptjs\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><a href=\"https:\/\/typegoose.github.io\/typegoose\/\" target=\"_blank\" rel=\"noreferrer noopener\">typegoose<\/a><\/code> &#8211; for defining Mongoose schemas with TypeScript classes and decorators.<\/li>\n\n\n\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/bcryptjs\" target=\"_blank\" rel=\"noreferrer noopener\">bcryptjs<\/a><\/code> &#8211; for hashing the user&#8217;s password<\/li>\n<\/ul>\n\n\n\n<p>Now create a <code>packages\/server\/src\/models\/user.model.ts<\/code> file and add the following code snippets:<\/p>\n\n\n\n<p><strong>packages\/server\/src\/models\/user.model.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport {\n  getModelForClass,\n  index,\n  modelOptions,\n  pre,\n  prop,\n} from '@typegoose\/typegoose';\nimport bcrypt from 'bcryptjs';\n\n@index({ email: 1 })\n@pre&lt;User&gt;('save', async function () {\n  \/\/ Hash password if the password is new or was updated\n  if (!this.isModified('password')) return;\n\n  \/\/ Hash password with costFactor of 12\n  this.password = await bcrypt.hash(this.password, 12);\n})\n@modelOptions({\n  schemaOptions: {\n    \/\/ Add createdAt and updatedAt fields\n    timestamps: true,\n  },\n})\n\n\/\/ Export the User class to be used as TypeScript type\nexport class User {\n  @prop()\n  name: string;\n\n  @prop({ unique: true, required: true })\n  email: string;\n\n  @prop({ required: true, minlength: 8, maxLength: 32, select: false })\n  password: string;\n\n  @prop({ default: 'user' })\n  role: string;\n\n  @prop({ required: true })\n  photo: string;\n\n  \/\/ Instance method to check if passwords match\n  async comparePasswords(hashedPassword: string, candidatePassword: string) {\n    return await bcrypt.compare(candidatePassword, hashedPassword);\n  }\n}\n\n\/\/ Create the user model from the User class\nconst userModel = getModelForClass&lt;typeof User&gt;(User);\nexport default userModel;\n<\/code>\n<\/pre>\n\n\n\n<p>Key things to note in the above code:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, we created a TypeScript class and defined the attributes of a user using the Typegoose <code>@prop()<\/code> decorators.<\/li>\n\n\n\n<li>We created a <code>comparePasswords()<\/code> instance method that will be available on the user model. Later, we will evoke it to validate the user&#8217;s password against the hashed one in the database.<\/li>\n\n\n\n<li>Next, we used the <code>@modelOptions()<\/code> decorator with some configurations to tell Mongoose to add the &#8220;<strong>createdAt<\/strong>&#8221; and &#8220;<strong>updatedAt<\/strong>&#8221; timestamps. <\/li>\n\n\n\n<li>Since we&#8217;ll be querying the database with the user&#8217;s email, we added an index to the email field to speed up the query.<\/li>\n\n\n\n<li>Next, we used the Typegoose <a href=\"https:\/\/typegoose.github.io\/typegoose\/docs\/api\/decorators\/hooks\/\" target=\"_blank\" rel=\"noreferrer noopener\">pre-save hook<\/a> to hash the user&#8217;s password before adding the document to the database.<\/li>\n\n\n\n<li>Lastly, we extracted the mongoose model from the TypeScript class using the <code>getModelForClass()<\/code> function provided by Typegoose.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Create the Schemas with Zod<\/h2>\n\n\n\n<p><strong>tRPC<\/strong> works out-of-the-box with popular validation libraries like <a href=\"https:\/\/www.npmjs.com\/package\/yup\" target=\"_blank\" rel=\"noreferrer noopener\">Yup<\/a>, <a href=\"https:\/\/www.npmjs.com\/package\/superstruct\" target=\"_blank\" rel=\"noreferrer noopener\">Superstruct<\/a>, <a href=\"https:\/\/www.npmjs.com\/package\/zod\" target=\"_blank\" rel=\"noreferrer noopener\">Zod<\/a>, and <a href=\"https:\/\/www.npmjs.com\/package\/myzod\" target=\"_blank\" rel=\"noreferrer noopener\">myzod<\/a>. It also allows you to use your own custom validators to validate the request body before the request gets to the resolvers.<\/p>\n\n\n\n<p>Despite having all those validators, we will use Zod since it&#8217;s a TypeScript-first schema declaration and validation library.<\/p>\n\n\n\n<p>Install the Zod library:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add zod\n<\/code>\n<\/pre>\n\n\n\n<p>Now create a <code>packages\/server\/src\/schemas\/user.schema.ts<\/code> file and add the following code:<\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { object, string, TypeOf } from 'zod';\n\nexport const createUserSchema = object({\n  name: string({ required_error: 'Name is required' }),\n  email: string({ required_error: 'Email is required' }).email('Invalid email'),\n  photo: string({ required_error: 'Photo is required' }),\n  password: string({ required_error: 'Password is required' })\n    .min(8, 'Password must be more than 8 characters')\n    .max(32, 'Password must be less than 32 characters'),\n  passwordConfirm: string({ required_error: 'Please confirm your password' }),\n}).refine((data) =&gt; data.password === data.passwordConfirm, {\n  path: ['passwordConfirm'],\n  message: 'Passwords do not match',\n});\n\nexport const loginUserSchema = object({\n  email: string({ required_error: 'Email is required' }).email(\n    'Invalid email or password'\n  ),\n  password: string({ required_error: 'Password is required' }).min(\n    8,\n    'Invalid email or password'\n  ),\n});\n\nexport type CreateUserInput = TypeOf&lt;typeof createUserSchema&gt;;\nexport type LoginUserInput = TypeOf&lt;typeof loginUserSchema&gt;;\n<\/code>\n<\/pre>\n\n\n\n<p>If you take a careful look at the above code, we retrieved the TypeScript types from the schemas using the <code>TypeOf&lt;&gt;<\/code> type provided by Zod. Later we will need those types when defining the controllers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating Utility Functions to Sign and Verify JWTs<\/h2>\n\n\n\n<p>Run this command to install <code>jsonwebtoken<\/code> package:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add jsonwebtoken &amp;&amp; yarn add -D @types\/jsonwebtoken\n<\/code>\n<\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>If you want to learn more about JWT Authentication with React, check out <a href=\"\/react-redux-toolkit-jwt-authentication-and-authorization\">React + Redux Toolkit: JWT Authentication and Authorization<\/a><\/p>\n<\/blockquote>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>If you want to learn more about Refresh Tokens with React, check out <a href=\"\/react-redux-toolkit-refresh-token-authentication\">React.js + Redux Toolkit: Refresh Tokens Authentication<\/a><\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">How to Generate the JWT Private and Public Keys<\/h3>\n\n\n\n<p>To reduce the complexity of this project, I already added the private and public keys to the <code>packages\/server\/.env<\/code> file but you can follow the steps below to generate them yourself.<\/p>\n\n\n\n<p><strong>Step 1:<\/strong> Go to <a href=\"http:\/\/travistidwell.com\/jsencrypt\/demo\/\" target=\"_blank\" rel=\"noreferrer noopener\">this website<\/a>, change the key size to <strong>512 bits<\/strong> and click the blue <strong>&#8220;Generate New Keys&#8221;<\/strong> button to generate the private and public keys.<\/p>\n\n\n\n<p><strong>Step 2:<\/strong> Copy the private key and visit <a href=\"https:\/\/www.base64encode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">this website<\/a> to convert it to <strong>Base64<\/strong>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>We are encoding the keys to avoid getting <strong>unnecessary warnings<\/strong> in the terminal when building the Docker images.<\/p>\n<\/blockquote>\n\n\n\n<p>Maybe you might not get the warnings in <strong>Node.js<\/strong> but when I was working in <strong>Python<\/strong> I was getting those warnings in the terminal.<\/p>\n\n\n\n<p><strong>Step 3:<\/strong> Copy the encoded key and add it to the <code>packages\/server\/.env<\/code> file as <code>ACCESS_TOKEN_PRIVATE_KEY<\/code> .<\/p>\n\n\n\n<p><strong>Step 4:<\/strong> Copy the corresponding public key and encode it before adding it to the <code>packages\/server\/.env<\/code> file as <code>ACCESS_TOKEN_PUBLIC_KEY<\/code> .<\/p>\n\n\n\n<p><strong>Step 5:<\/strong> Repeat the process for the refresh token private and public keys.<\/p>\n\n\n\n<p>After adding the private and public keys to the environment variables file, update the <code>packages\/server\/src\/config\/default.ts<\/code> file to enable the <code>dotenv<\/code> package to load them into the Node.js environment.<\/p>\n\n\n\n<p><strong>packages\/server\/src\/config\/default.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport path from 'path';\nrequire('dotenv').config({ path: path.join(__dirname, '..\/..\/.env') });\n\nconst customConfig: {\n  port: number;\n  accessTokenExpiresIn: number;\n  refreshTokenExpiresIn: number;\n  origin: string;\n  dbUri: string;\n  accessTokenPrivateKey: string;\n  accessTokenPublicKey: string;\n  refreshTokenPrivateKey: string;\n  refreshTokenPublicKey: string;\n} = {\n  port: 8000,\n  accessTokenExpiresIn: 15,\n  refreshTokenExpiresIn: 60,\n  origin: 'http:\/\/localhost:3000',\n\n  dbUri: process.env.MONGODB_URI as string,\n  accessTokenPrivateKey: process.env.ACCESS_TOKEN_PRIVATE_KEY as string,\n  accessTokenPublicKey: process.env.ACCESS_TOKEN_PUBLIC_KEY as string,\n  refreshTokenPrivateKey: process.env.REFRESH_TOKEN_PRIVATE_KEY as string,\n  refreshTokenPublicKey: process.env.REFRESH_TOKEN_PUBLIC_KEY as string,\n};\n\nexport default customConfig;\n<\/code>\n<\/pre>\n\n\n\n<p>With that out of the way, let&#8217;s create two functions to generate and verify the tokens with the <code>jsonwebtoken<\/code> package. <\/p>\n\n\n\n<p>Create a <code>packages\/server\/src\/utils\/jwt.ts<\/code> file and add the following:<\/p>\n\n\n\n<p><strong>packages\/server\/src\/utils\/jwt.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport jwt, { SignOptions } from 'jsonwebtoken';\nimport customConfig from '..\/config\/default';\n\nexport const signJwt = (\n  payload: Object,\n  key: 'accessTokenPrivateKey' | 'refreshTokenPrivateKey',\n  options: SignOptions = {}\n) =&gt; {\n  const privateKey = Buffer.from(customConfig[key], 'base64').toString('ascii');\n  return jwt.sign(payload, privateKey, {\n    ...(options &amp;&amp; options),\n    algorithm: 'RS256',\n  });\n};\n\nexport const verifyJwt = &lt;T&gt;(\n  token: string,\n  key: 'accessTokenPublicKey' | 'refreshTokenPublicKey'\n): T | null =&gt; {\n  try {\n    const publicKey = Buffer.from(customConfig[key], 'base64').toString(\n      'ascii'\n    );\n    return jwt.verify(token, publicKey) as T;\n  } catch (error) {\n    console.log(error);\n    return null;\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<p>In the above, you&#8217;ll notice that we decoded the encoded private and public keys back to <strong>ASCII<\/strong> strings before passing them to the <code>jsonwebtoken<\/code> methods.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Database Services<\/h2>\n\n\n\n<p>When designing an API architecture, it&#8217;s always a good practice to separate the business logic from the actual implementation.<\/p>\n\n\n\n<p>In other words, you should define services to query and mutate the database. That means the controllers should not have direct access to the database. <\/p>\n\n\n\n<p>The recommendation is to push most of the business logic to the models or services, leaving us with thin controllers and large models or services.<\/p>\n\n\n\n<p>Now install the <code>lodash<\/code> package to help us filter the data returned by the database to avoid sending sensitive information to the user.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add lodash &amp;&amp; yarn add -D @types\/lodash\n<\/code>\n<\/pre>\n\n\n\n<p>Next, create a <code>packages\/server\/src\/services\/user.service.ts<\/code> file and add the following code:<\/p>\n\n\n\n<p><strong>packages\/server\/src\/services\/user.service.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { omit } from 'lodash';\nimport { FilterQuery, QueryOptions } from 'mongoose';\nimport userModel, { User } from '..\/models\/user.model';\nimport { signJwt } from '..\/utils\/jwt';\nimport redisClient from '..\/utils\/connectRedis';\nimport { DocumentType } from '@typegoose\/typegoose';\nimport customConfig from '..\/config\/default';\n\n\/\/ Exclude this fields from the response\nexport const excludedFields = ['password'];\n\n\/\/ CreateUser service\nexport const createUser = async (input: Partial&lt;User&gt;) =&gt; {\n  const user = await userModel.create(input);\n  return omit(user.toJSON(), excludedFields);\n};\n\n\/\/ Find User by Id\nexport const findUserById = async (id: string) =&gt; {\n  return await userModel.findById(id).lean();\n};\n\n\/\/ Find All users\nexport const findAllUsers = async () =&gt; {\n  return await userModel.find();\n};\n\n\/\/ Find one user by any fields\nexport const findUser = async (\n  query: FilterQuery&lt;User&gt;,\n  options: QueryOptions = {}\n) =&gt; {\n  return await userModel.findOne(query, {}, options).select('+password');\n};\n\n\/\/ Sign Token\nexport const signToken = async (user: DocumentType&lt;User&gt;) =&gt; {\n  const userId = user._id.toString();\n  \/\/ Sign the access token\n  const access_token = signJwt({ sub: userId }, 'accessTokenPrivateKey', {\n    expiresIn: `${customConfig.accessTokenExpiresIn}m`,\n  });\n\n  \/\/ Sign the refresh token\n  const refresh_token = signJwt({ sub: userId }, 'refreshTokenPrivateKey', {\n    expiresIn: `${customConfig.refreshTokenExpiresIn}m`,\n  });\n\n  \/\/ Create a Session\n  redisClient.set(userId, JSON.stringify(user), {\n    EX: 60 * 60,\n  });\n\n  \/\/ Return access token\n  return { access_token, refresh_token };\n};\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Authentication Controllers<\/h2>\n\n\n\n<p>We are now ready to create the authentication controllers to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Register a new user<\/li>\n\n\n\n<li>Refresh the access token<\/li>\n\n\n\n<li>Sign in the registered user<\/li>\n\n\n\n<li>Logout the authenticated user<\/li>\n<\/ol>\n\n\n\n<p>Before that, let&#8217;s create the cookie options. Create a <code>packages\/server\/src\/controllers\/auth.controller.ts<\/code> file and add the following:<\/p>\n\n\n\n<p><strong>packages\/server\/src\/controllers\/auth.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { TRPCError } from '@trpc\/server';\nimport { CookieOptions } from 'express';\nimport { Context } from '..\/app';\nimport customConfig from '..\/config\/default';\nimport { CreateUserInput, LoginUserInput } from '..\/schema\/user.schema';\nimport {\n  createUser,\n  findUser,\n  findUserById,\n  signToken,\n} from '..\/services\/user.service';\nimport redisClient from '..\/utils\/connectRedis';\nimport { signJwt, verifyJwt } from '..\/utils\/jwt';\n\nconst cookieOptions: CookieOptions = {\n  httpOnly: true,\n  secure: process.env.NODE_ENV === 'production',\n  sameSite: 'lax',\n};\n\n\/\/ Cookie options\nconst accessTokenCookieOptions = {\n  ...cookieOptions,\n  expires: new Date(Date.now() + customConfig.accessTokenExpiresIn * 60 * 1000),\n};\n\nconst refreshTokenCookieOptions = {\n  ...cookieOptions,\n  expires: new Date(\n    Date.now() + customConfig.refreshTokenExpiresIn * 60 * 1000\n  ),\n};\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Register User Controller<\/h3>\n\n\n\n<p><strong>packages\/server\/src\/controllers\/auth.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\n\/\/ Imports [...]\n\n\/\/ Cookie options [...]\n\n\/\/ Register a new user\nexport const registerHandler = async ({\n  input,\n}: {\n  input: CreateUserInput;\n}) =&gt; {\n  try {\n    const user = await createUser({\n      email: input.email,\n      name: input.name,\n      password: input.password,\n      photo: input.photo,\n    });\n\n    return {\n      status: 'success',\n      data: {\n        user,\n      },\n    };\n  } catch (err: any) {\n    if (err.code === 11000) {\n      throw new TRPCError({\n        code: 'CONFLICT',\n        message: 'Email already exists',\n      });\n    }\n    throw err;\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<p>In the above, we called the <code>createUser()<\/code> service we defined to add the new user to the database.<\/p>\n\n\n\n<p>Also, since we added a unique constraint to the email field, MongoDB will return an error with a <code>11000<\/code> code indicating that a user with that email already exists in the database. So it makes a lot of sense to catch that error to prevent the application from crashing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Sign in User Controller<\/h3>\n\n\n\n<p>Since we are able to register a user, let&#8217;s create the <strong>tRPC<\/strong> controller to sign in the registered user.<\/p>\n\n\n\n<p>In a future article, we will add email verification logic to the authentication flow.<\/p>\n\n\n\n<p><strong>packages\/server\/src\/controllers\/auth.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\n\/\/ Imports [...]\n\n\/\/ Cookie options [...]\n\n\/\/ Register a new user\n\n\/\/ Login a user\nexport const loginHandler = async ({\n  input,\n  ctx,\n}: {\n  input: LoginUserInput;\n  ctx: Context;\n}) =&gt; {\n  try {\n    \/\/ Get the user from the collection\n    const user = await findUser({ email: input.email });\n\n    \/\/ Check if user exist and password is correct\n    if (\n      !user ||\n      !(await user.comparePasswords(user.password, input.password))\n    ) {\n      throw new TRPCError({\n        code: 'BAD_REQUEST',\n        message: 'Invalid email or password',\n      });\n    }\n\n    \/\/ Create the Access and refresh Tokens\n    const { access_token, refresh_token } = await signToken(user);\n\n    \/\/ Send Access Token in Cookie\n    ctx.res.cookie('access_token', access_token, accessTokenCookieOptions);\n    ctx.res.cookie('refresh_token', refresh_token, refreshTokenCookieOptions);\n    ctx.res.cookie('logged_in', true, {\n      ...accessTokenCookieOptions,\n      httpOnly: false,\n    });\n\n    \/\/ Send Access Token\n    return {\n      status: 'success',\n      access_token,\n    };\n  } catch (err: any) {\n    throw err;\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<p>Below is what we did above:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, we called the <code>findUser()<\/code> service to check if a user with that email exists in the database.<\/li>\n\n\n\n<li>Next, we called the <code>comparePasswords()<\/code> instance method we added to the user class to validate the provided password against the hashed one in the database.<\/li>\n\n\n\n<li>Lastly, we generated the access and refresh tokens and returned them to the user&#8217;s browser or client as <strong>HTTPOnly<\/strong> cookies.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Refresh Access Token Controller<\/h3>\n\n\n\n<p>Now let&#8217;s create a <strong>tRPC<\/strong> handler that will be called to generate a new access token.<\/p>\n\n\n\n<p><strong>packages\/server\/src\/controllers\/auth.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\n\/\/ Imports [...]\n\n\/\/ Cookie options [...]\n\n\/\/ Register a new user\n\n\/\/ Login a user\n\n\/\/ Refresh tokens handler\nexport const refreshAccessTokenHandler = async ({ ctx }: { ctx: Context }) =&gt; {\n  try {\n    \/\/ Get the refresh token from cookie\n    const refresh_token = ctx.req.cookies.refresh_token as string;\n\n    const message = 'Could not refresh access token';\n    if (!refresh_token) {\n      throw new TRPCError({ code: 'FORBIDDEN', message });\n    }\n\n    \/\/ Validate the Refresh token\n    const decoded = verifyJwt&lt;{ sub: string }&gt;(\n      refresh_token,\n      'refreshTokenPublicKey'\n    );\n\n    if (!decoded) {\n      throw new TRPCError({ code: 'FORBIDDEN', message });\n    }\n\n    \/\/ Check if the user has a valid session\n    const session = await redisClient.get(decoded.sub);\n    if (!session) {\n      throw new TRPCError({ code: 'FORBIDDEN', message });\n    }\n\n    \/\/ Check if the user exist\n    const user = await findUserById(JSON.parse(session)._id);\n\n    if (!user) {\n      throw new TRPCError({ code: 'FORBIDDEN', message });\n    }\n\n    \/\/ Sign new access token\n    const access_token = signJwt({ sub: user._id }, 'accessTokenPrivateKey', {\n      expiresIn: `${customConfig.accessTokenExpiresIn}m`,\n    });\n\n    \/\/ Send the access token as cookie\n    ctx.res.cookie('access_token', access_token, accessTokenCookieOptions);\n    ctx.res.cookie('logged_in', true, {\n      ...accessTokenCookieOptions,\n      httpOnly: false,\n    });\n\n    \/\/ Send response\n    return {\n      status: 'success',\n      access_token,\n    };\n  } catch (err: any) {\n    throw err;\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Logout User Controller<\/h3>\n\n\n\n<p>Finally, let&#8217;s create a handler to log out the user on both the <strong>tRPC<\/strong> client and server.<\/p>\n\n\n\n<p>To log out the user on the <strong>tRPC<\/strong> client, we will return expired cookies to the client.<\/p>\n\n\n\n<p><strong>packages\/server\/src\/controllers\/auth.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\n\/\/ Imports [...]\n\n\/\/ Cookie options [...]\n\n\/\/ Register a new user\n\n\/\/ Login a user\n\n\/\/ Refresh tokens handler\n\n\/\/ Logout handler\nconst logout = ({ ctx }: { ctx: Context }) =&gt; {\n  ctx.res.cookie('access_token', '', { maxAge: -1 });\n  ctx.res.cookie('refresh_token', '', { maxAge: -1 });\n  ctx.res.cookie('logged_in', '', {\n    maxAge: -1,\n  });\n};\nexport const logoutHandler = async ({ ctx }: { ctx: Context }) =&gt; {\n  try {\n    const user = ctx.user;\n    await redisClient.del(user?._id.toString());\n    logout({ ctx });\n    return { status: 'success' };\n  } catch (err: any) {\n    throw err;\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Complete Code for the Authentication Controllers<\/h3>\n\n\n\n<p><strong>packages\/server\/src\/controllers\/auth.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { TRPCError } from '@trpc\/server';\nimport { CookieOptions } from 'express';\nimport { Context } from '..\/app';\nimport customConfig from '..\/config\/default';\nimport { CreateUserInput, LoginUserInput } from '..\/schema\/user.schema';\nimport {\n  createUser,\n  findUser,\n  findUserById,\n  signToken,\n} from '..\/services\/user.service';\nimport redisClient from '..\/utils\/connectRedis';\nimport { signJwt, verifyJwt } from '..\/utils\/jwt';\n\/\/ Imports [...]\n\n\/\/ Cookie options [...]\nconst cookieOptions: CookieOptions = {\n  httpOnly: true,\n  secure: process.env.NODE_ENV === 'production',\n  sameSite: 'lax',\n};\n\nconst accessTokenCookieOptions = {\n  ...cookieOptions,\n  expires: new Date(Date.now() + customConfig.accessTokenExpiresIn * 60 * 1000),\n};\n\nconst refreshTokenCookieOptions = {\n  ...cookieOptions,\n  expires: new Date(\n    Date.now() + customConfig.refreshTokenExpiresIn * 60 * 1000\n  ),\n};\n\n\/\/ Register a new user\nexport const registerHandler = async ({\n  input,\n}: {\n  input: CreateUserInput;\n}) =&gt; {\n  try {\n    const user = await createUser({\n      email: input.email,\n      name: input.name,\n      password: input.password,\n      photo: input.photo,\n    });\n\n    return {\n      status: 'success',\n      data: {\n        user,\n      },\n    };\n  } catch (err: any) {\n    if (err.code === 11000) {\n      throw new TRPCError({\n        code: 'CONFLICT',\n        message: 'Email already exists',\n      });\n    }\n    throw err;\n  }\n};\n\n\/\/ Login a user\nexport const loginHandler = async ({\n  input,\n  ctx,\n}: {\n  input: LoginUserInput;\n  ctx: Context;\n}) =&gt; {\n  try {\n    \/\/ Get the user from the collection\n    const user = await findUser({ email: input.email });\n\n    \/\/ Check if user exist and password is correct\n    if (\n      !user ||\n      !(await user.comparePasswords(user.password, input.password))\n    ) {\n      throw new TRPCError({\n        code: 'BAD_REQUEST',\n        message: 'Invalid email or password',\n      });\n    }\n\n    \/\/ Create the Access and refresh Tokens\n    const { access_token, refresh_token } = await signToken(user);\n\n    \/\/ Send Access Token in Cookie\n    ctx.res.cookie('access_token', access_token, accessTokenCookieOptions);\n    ctx.res.cookie('refresh_token', refresh_token, refreshTokenCookieOptions);\n    ctx.res.cookie('logged_in', true, {\n      ...accessTokenCookieOptions,\n      httpOnly: false,\n    });\n\n    \/\/ Send Access Token\n    return {\n      status: 'success',\n      access_token,\n    };\n  } catch (err: any) {\n    throw err;\n  }\n};\n\n\/\/ Refresh tokens handler\nexport const refreshAccessTokenHandler = async ({ ctx }: { ctx: Context }) =&gt; {\n  try {\n    \/\/ Get the refresh token from cookie\n    const refresh_token = ctx.req.cookies.refresh_token as string;\n\n    const message = 'Could not refresh access token';\n    if (!refresh_token) {\n      throw new TRPCError({ code: 'FORBIDDEN', message });\n    }\n\n    \/\/ Validate the Refresh token\n    const decoded = verifyJwt&lt;{ sub: string }&gt;(\n      refresh_token,\n      'refreshTokenPublicKey'\n    );\n\n    if (!decoded) {\n      throw new TRPCError({ code: 'FORBIDDEN', message });\n    }\n\n    \/\/ Check if the user has a valid session\n    const session = await redisClient.get(decoded.sub);\n    if (!session) {\n      throw new TRPCError({ code: 'FORBIDDEN', message });\n    }\n\n    \/\/ Check if the user exist\n    const user = await findUserById(JSON.parse(session)._id);\n\n    if (!user) {\n      throw new TRPCError({ code: 'FORBIDDEN', message });\n    }\n\n    \/\/ Sign new access token\n    const access_token = signJwt({ sub: user._id }, 'accessTokenPrivateKey', {\n      expiresIn: `${customConfig.accessTokenExpiresIn}m`,\n    });\n\n    \/\/ Send the access token as cookie\n    ctx.res.cookie('access_token', access_token, accessTokenCookieOptions);\n    ctx.res.cookie('logged_in', true, {\n      ...accessTokenCookieOptions,\n      httpOnly: false,\n    });\n\n    \/\/ Send response\n    return {\n      status: 'success',\n      access_token,\n    };\n  } catch (err: any) {\n    throw err;\n  }\n};\n\n\/\/ Logout handler\nconst logout = ({ ctx }: { ctx: Context }) =&gt; {\n  ctx.res.cookie('access_token', '', { maxAge: -1 });\n  ctx.res.cookie('refresh_token', '', { maxAge: -1 });\n  ctx.res.cookie('logged_in', '', {\n    maxAge: -1,\n  });\n};\nexport const logoutHandler = async ({ ctx }: { ctx: Context }) =&gt; {\n  try {\n    const user = ctx.user;\n    await redisClient.del(user?._id.toString());\n    logout({ ctx });\n    return { status: 'success' };\n  } catch (err: any) {\n    throw err;\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create a User Controller<\/h2>\n\n\n\n<p>Let&#8217;s create a <code>getMe<\/code> handler to return the authenticated user&#8217;s information we will be storing in the <strong>tRPC<\/strong> context.<\/p>\n\n\n\n<p>Create a <code>packages\/server\/src\/controllers\/user.controller.ts<\/code> file and add the following code:<\/p>\n\n\n\n<p><strong>packages\/server\/src\/controllers\/user.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { TRPCError } from '@trpc\/server';\nimport { Context } from '..\/app';\n\nexport const getMeHandler = ({ ctx }: { ctx: Context }) =&gt; {\n  try {\n    const user = ctx.user;\n    return {\n      status: 'success',\n      data: {\n        user,\n      },\n    };\n  } catch (err: any) {\n    throw new TRPCError({\n      code: 'INTERNAL_SERVER_ERROR',\n      message: err.message,\n    });\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Authentication Middleware<\/h2>\n\n\n\n<p><strong>tRPC<\/strong> allows us to add middleware(s) to a whole router with the&nbsp;<code>middleware()<\/code>&nbsp;method.<\/p>\n\n\n\n<p>This middleware will be called on protected routes to ensure that a valid access token was included in the request.<\/p>\n\n\n\n<p><strong>packages\/server\/src\/middleware\/deserializeUser.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { TRPCError } from '@trpc\/server';\nimport * as trpcExpress from '@trpc\/server\/adapters\/express';\nimport { findUserById } from '..\/services\/user.service';\nimport redisClient from '..\/utils\/connectRedis';\nimport { verifyJwt } from '..\/utils\/jwt';\n\nexport const deserializeUser = async ({\n  req,\n  res,\n}: trpcExpress.CreateExpressContextOptions) =&gt; {\n  try {\n    \/\/ Get the token\n    let access_token;\n    if (\n      req.headers.authorization &amp;&amp;\n      req.headers.authorization.startsWith('Bearer')\n    ) {\n      access_token = req.headers.authorization.split(' ')[1];\n    } else if (req.cookies.access_token) {\n      access_token = req.cookies.access_token;\n    }\n\n    const notAuthenticated = {\n      req,\n      res,\n      user: null,\n    };\n\n    if (!access_token) {\n      return notAuthenticated;\n    }\n\n    \/\/ Validate Access Token\n    const decoded = verifyJwt&lt;{ sub: string }&gt;(\n      access_token,\n      'accessTokenPublicKey'\n    );\n\n    if (!decoded) {\n      return notAuthenticated;\n    }\n\n    \/\/ Check if user has a valid session\n    const session = await redisClient.get(decoded.sub);\n\n    if (!session) {\n      return notAuthenticated;\n    }\n\n    \/\/ Check if user still exist\n    const user = await findUserById(JSON.parse(session)._id);\n\n    if (!user) {\n      return notAuthenticated;\n    }\n\n    return {\n      req,\n      res,\n      user: { ...user, id: user._id.toString() },\n    };\n  } catch (err: any) {\n    throw new TRPCError({\n      code: 'INTERNAL_SERVER_ERROR',\n      message: err.message,\n    });\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Routers in the App File<\/h2>\n\n\n\n<p>Install the following packages:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add cookie-parser &amp;&amp; yarn add -D @types\/cookie-parser morgan @types\/morgan\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/cookie-parser\" target=\"_blank\" rel=\"noreferrer noopener\">cookie-parser<\/a><\/code> &#8211; Parses the&nbsp;<code>Cookie<\/code>&nbsp;header and populate&nbsp;<code>req.cookies<\/code>&nbsp;with an object keyed by the cookie names.<\/li>\n\n\n\n<li><code><a href=\"https:\/\/www.npmjs.com\/package\/morgan\" target=\"_blank\" rel=\"noreferrer noopener\">morgan<\/a><\/code> &#8211; for logging the <strong>tRPC<\/strong> requests in the terminal<\/li>\n<\/ul>\n\n\n\n<p>Now update the <code>packages\/server\/src\/app.ts<\/code> file to add the <code>authRouter<\/code> and <code>userRouter<\/code> to the app router.<\/p>\n\n\n\n<p>Also, you need to add the cookie parser middleware to the middleware stack to enable it to parse the cookies in the request headers.<\/p>\n\n\n\n<p><strong>packages\/server\/src\/app.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport path from &quot;path&quot;;\nimport dotenv from &quot;dotenv&quot;;\nimport express from &quot;express&quot;;\nimport morgan from &quot;morgan&quot;;\nimport cors from &quot;cors&quot;;\nimport cookieParser from &quot;cookie-parser&quot;;\nimport { inferAsyncReturnType, initTRPC, TRPCError } from &quot;@trpc\/server&quot;;\nimport * as trpcExpress from &quot;@trpc\/server\/adapters\/express&quot;;\nimport { createUserSchema, loginUserSchema } from &quot;.\/schema\/user.schema&quot;;\nimport {\n  loginHandler,\n  logoutHandler,\n  refreshAccessTokenHandler,\n  registerHandler,\n} from &quot;.\/controllers\/auth.controller&quot;;\nimport { getMeHandler } from &quot;.\/controllers\/user.controller&quot;;\nimport { deserializeUser } from &quot;.\/middleware\/deserializeUser&quot;;\nimport connectDB from &quot;.\/utils\/connectDB&quot;;\nimport customConfig from &quot;.\/config\/default&quot;;\n\ndotenv.config({ path: path.join(__dirname, &quot;.\/.env&quot;) });\n\nconst createContext = ({ req, res }: trpcExpress.CreateExpressContextOptions) =&gt;\n  deserializeUser({ req, res });\n\nexport type Context = inferAsyncReturnType&lt;typeof createContext&gt;;\n\nexport const t = initTRPC.context&lt;Context&gt;().create();\n\nconst authRouter = t.router({\n  registerUser: t.procedure\n    .input(createUserSchema)\n    .mutation(({ input }) =&gt; registerHandler({ input })),\n  loginUser: t.procedure\n    .input(loginUserSchema)\n    .mutation(({ input, ctx }) =&gt; loginHandler({ input, ctx })),\n  logoutUser: t.procedure.mutation(({ ctx }) =&gt; logoutHandler({ ctx })),\n  refreshToken: t.procedure.query(({ ctx }) =&gt;\n    refreshAccessTokenHandler({ ctx })\n  ),\n});\n\nconst isAuthorized = t.middleware(({ ctx, next }) =&gt; {\n  if (!ctx.user) {\n    throw new TRPCError({\n      code: &quot;UNAUTHORIZED&quot;,\n      message: &quot;You must be logged in to access this resource&quot;,\n    });\n  }\n  return next();\n});\n\nconst isAuthorizedProcedure = t.procedure.use(isAuthorized);\n\nconst userRouter = t.router({\n  sayHello: t.procedure.query(async () =&gt; {\n    const message = &quot;Welcome to tRPC with React and Node&quot;;\n    return { message };\n  }),\n  getMe: isAuthorizedProcedure.query(({ ctx }) =&gt; getMeHandler({ ctx })),\n});\n\nconst appRouter = t.mergeRouters(authRouter, userRouter);\n\nexport type AppRouter = typeof appRouter;\n\nconst app = express();\nif (process.env.NODE_ENV !== &quot;production&quot;) app.use(morgan(&quot;dev&quot;));\n\napp.use(cookieParser());\napp.use(\n  cors({\n    origin: [customConfig.origin],\n    credentials: true,\n  })\n);\napp.use(\n  &quot;\/api\/trpc&quot;,\n  trpcExpress.createExpressMiddleware({\n    router: appRouter,\n    createContext,\n  })\n);\n\nconst port = customConfig.port;\napp.listen(port, () =&gt; {\n  console.log(`\ud83d\ude80 Server listening on port ${port}`);\n\n  \/\/ CONNECT DB\n  connectDB();\n});\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Congrats on reaching the end. In this article, you learned how to implement access and refresh tokens in a <strong>tRPC<\/strong> API using Node.js, Express, MongoDB, Typegoose, Docker, and Redis.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">tRPC, Node.js, MongoDB, and Express Source Code<\/h2>\n\n\n\n<p>Check out the:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/wpcodevo\/trpc-react-node-mongodb\/tree\/trpc-node-auth\" target=\"_blank\" rel=\"noreferrer noopener\">tRPC Node Express API<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/wpcodevo\/trpc-react-node-mongodb\/tree\/trpc-node-react-auth\" target=\"_blank\" rel=\"noreferrer noopener\">React and Node tRPC Source Code<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>When we build a full-stack TypeScript application, it becomes challenging to share types directly between the client and server, without depending on code generation or&#8230;<\/p>\n","protected":false},"author":1,"featured_media":4344,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[47],"tags":[42],"class_list":["post-4263","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-nodejs","tag-nodejs-api"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4263","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/comments?post=4263"}],"version-history":[{"count":1,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4263\/revisions"}],"predecessor-version":[{"id":11296,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4263\/revisions\/11296"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/4344"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=4263"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=4263"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=4263"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}