{"id":5380,"date":"2022-07-23T14:58:56","date_gmt":"2022-07-23T14:58:56","guid":{"rendered":"https:\/\/codevoweb.com\/?p=5380"},"modified":"2022-10-26T09:40:35","modified_gmt":"2022-10-26T09:40:35","slug":"trpc-api-with-postgres-prisma-nodejs-jwt-authentication","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/trpc-api-with-postgres-prisma-nodejs-jwt-authentication\/","title":{"rendered":"tRPC API with Postgres, Prisma &#038; Node.js: JWT Authentication"},"content":{"rendered":"\n<p>This article will teach you how to add JSON Web Token (JWT) Authentication to a <a href=\"https:\/\/trpc.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">tRPC<\/a> API built with PostgreSQL, Prisma, Express, Node.js, and Redis.<\/p>\n\n\n\n<p>When we build full-stack applications with TypeScript, we end up developing different strategies to share the static types between the frontend and backend, this approach works for a few genius developers.<\/p>\n\n\n\n<p>Those who fall into the vast majority of people either end up increasing the complexity of their project or wasting productive hours writing unnecessary code.<\/p>\n\n\n\n<p>Up until now, GraphQL has been the dominant library used by developers to build type-safety full-stack applications.<\/p>\n\n\n\n<p>GraphQL was a game changer for most developers since it came with more flexibility and control.<\/p>\n\n\n\n<p>However, the flexibility GraphQL brought to developers came at a price in the form of extra complexity and the need to learn the query language.<\/p>\n\n\n\n<p>Due to the complexities of GraphQL, frameworks, libraries, and services started popping up to streamline the development process, and provide better patterns.<\/p>\n\n\n\n<p>Recently, <strong>tRPC<\/strong> was introduced to help developers statically type their API endpoints and directly share the types between the client and server.<\/p>\n\n\n\n<p>tRPC is lightweight, has <a href=\"https:\/\/trpc.io\/#zero\" target=\"_blank\" rel=\"noreferrer noopener\">zero dependencies<\/a>, doesn&#8217;t rely on code generation, leaves a tiny client-side footprint, and has adaptors for <strong>Fastify<\/strong>\/<strong>Express<\/strong>\/<strong>Next.js<\/strong>.<\/p>\n\n\n\n<p>tRPC API with React.js, Express, Prisma, and PostgreSQL Series:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li><a href=\"\/setup-trpc-api-with-prisma-postgresql-nodejs-reactjs\">How to Setup tRPC API with Prisma, PostgreSQL, Node &amp; React<\/a><\/li><li><a href=\"\/trpc-api-with-postgres-prisma-nodejs-jwt-authentication\"><\/a><a href=\"\/trpc-api-with-postgres-prisma-nodejs-jwt-authentication\">tRPC API with PostgreSQL, Prisma, Redis, &amp; Node.js: JWT Authentication<\/a><\/li><\/ol>\n\n\n\n<p>Related Articles:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/golang-mongodb-jwt-authentication-authorization\">Golang &amp; MongoDB: JWT Authentication and Authorization<\/a><\/li><li><a href=\"\/api-golang-mongodb-send-html-emails-gomail\">API with Golang + MongoDB: Send HTML Emails with Gomail<\/a><\/li><li><a href=\"\/api-golang-gin-gonic-mongodb-forget-reset-password\">API with Golang, Gin Gonic &amp; MongoDB: Forget\/Reset Password<\/a><\/li><\/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\/tRPC-API-with-Postgres-Prisma-Node.js-JWT-Authentication.webp\" alt=\"tRPC API with Postgres, Prisma &amp; Node.js JWT Authentication\" class=\"wp-image-5450\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-API-with-Postgres-Prisma-Node.js-JWT-Authentication.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-API-with-Postgres-Prisma-Node.js-JWT-Authentication-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-API-with-Postgres-Prisma-Node.js-JWT-Authentication-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-API-with-Postgres-Prisma-Node.js-JWT-Authentication-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-API-with-Postgres-Prisma-Node.js-JWT-Authentication-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_0de06d-4e .kb-table-of-content-wrap{padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;border-color:#abb8c3;border-top-width:1px;border-right-width:1px;border-bottom-width:1px;border-left-width:1px;}.kb-table-of-content-nav.kb-table-of-content-id_0de06d-4e .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_0de06d-4e .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_0de06d-4e .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_0de06d-4e .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;}<\/style>\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before we begin:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Basic knowledge of Node.js, and Express will be helpful.<\/li><li>An intermediate understanding of Prisma, and how ORMs and relational databases work will be highly beneficial.<\/li><li>Have Docker and Node.js installed<\/li><\/ul>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-38\"><\/span>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up the Project<\/h2>\n\n\n\n<p>We will be using Express, Prisma, PostgreSQL, and tRPC along with the Express adaptor to implement the authentication flow.<\/p>\n\n\n\n<p>Also, we will be using PostgreSQL as the primary database and Redis to store the authenticated user&#8217;s session. Despite implementing the authentication with JSON Web Tokens, we need to include Redis to add an extra layer of security.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Follow the <a href=\"\/setup-trpc-api-with-prisma-postgresql-nodejs-reactjs\">Project Setup<\/a> tutorial to set up the <strong>tRPC<\/strong> project with PostgreSQL, Express and Prisma before continuing with this article.<\/p><\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Database Models with Prisma<\/h2>\n\n\n\n<p>Prisma is an open-source ORM (Object Relational Mapping) for Node.js and TypeScript.<\/p>\n\n\n\n<p>At the time of writing this article, Prisma supports PostgreSQL, MySQL, SQLite, SQL Server, MongoDB, and CockroachDB &#8211; making it the dominant ORM for any kind of project.<\/p>\n\n\n\n<p>Before we start creating the database models, change the directory into the <code>packages\/client<\/code> folder and install the following dependencies:<\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nyarn add -D prisma &amp;&amp; yarn add @prisma\/client\n<\/code>\n<\/pre>\n\n\n\n<p>Next, run the Prisma <code>init<\/code> command with an optional <code>--datasource-provider<\/code> parameter to set the type of database.<\/p>\n\n\n\n<p>By default, the <strong>init<\/strong> command creates a PostgreSQL database, however, you can use the <code>--datasource-provider<\/code> parameter to change the database type.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nnpx prisma init --datasource-provider postgresql\n<\/code>\n<\/pre>\n\n\n\n<p>The above command will create a <code>prisma<\/code>&nbsp;folder and either create a new <code>.env<\/code> file or update an existing one.<\/p>\n\n\n\n<p>The newly-created <code>packages\/server\/prisma\/schema.prisma<\/code> file will contain the configurations to connect to the database and the models needed to generate the database tables.<\/p>\n\n\n\n<p>With Prisma, we define all our schemas or models in the <code>packages\/server\/prisma\/schema.prisma<\/code> file.<\/p>\n\n\n\n<p>Let&#8217;s create a <code>User<\/code> model and provide it with all the required attributes:<\/p>\n\n\n\n<p><strong>packages\/server\/prisma\/schema.prisma<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n\ndatasource db {\n  provider = \"postgresql\"\n  url      = env(\"DATABASE_URL\")\n}\n\n\nmodel User{\n  @@map(name: \"users\")\n\n  id String  @id @default(uuid())\n  name String  @db.VarChar(255)\n  email String @unique\n  photo String? @default(\"default.png\")\n  verified Boolean? @default(false) \n  \n  password String\n  role RoleEnumType? @default(user)\n\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n\n  provider String?\n}\n\nenum RoleEnumType {\n  user\n  admin\n}\n<\/code>\n<\/pre>\n\n\n\n<p>Next, open the <code>packages\/server\/.env<\/code> file and update the dummy <code>DATABASE_URL<\/code> added by Prisma with the database credentials.<\/p>\n\n\n\n<p><strong>packages\/server\/.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nNODE_ENV=development\n\nORIGIN=http:\/\/127.0.0.1:3000\n\nDATABASE_URL=\"postgresql:\/\/postgres:password123@localhost:6500\/trpc_prisma?schema=public\"\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Database Migration with Prisma<\/h3>\n\n\n\n<p>Before we can run the database migration command, you need to have a running instance of a PostgreSQL database.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>Follow the <a href=\"\/setup-trpc-api-with-prisma-postgresql-nodejs-reactjs\">Project Setup<\/a> tutorial to set up the <strong>tRPC<\/strong> project with PostgreSQL, Express, Docker, and Prisma before continuing with this article.<\/p><\/blockquote>\n\n\n\n<p>Now add the following scripts to the <code>packages\/server\/package.json<\/code> file:<\/p>\n\n\n\n<pre class=\"line-numbers language-json\"><code>\n{\n\"scripts\": {\n    \"start\": \"ts-node-dev --respawn --transpile-only src\/app.ts\",\n    \"db:migrate\": \"npx prisma migrate dev --name user-entity --create-only &amp;&amp; yarn prisma generate\",\n    \"db:push\": \"npx prisma db push\"\n  },\n}\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\"><li><code>db:migrate<\/code> &#8211; this script will run the Prisma <code>migrate<\/code> command to create a new migration file and also generate the TypeScript types based on the models defined in the <code>schema.prisma<\/code> file.<\/li><li><code>db:push<\/code> &#8211; this script will run the Prisma <code>db<\/code> command to push the changes to the database and keep the database in sync with the Prisma schema.<\/li><\/ul>\n\n\n\n<p>Open the terminal and run the following commands to generate the migration file and push the schema to the database.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn db:migrate &amp;&amp; yarn db:push\n<\/code>\n<\/pre>\n\n\n\n<p>If you are familiar with <strong>pgAdmin<\/strong>, you can log in with the database credentials to see the table added by Prisma.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1005\" height=\"688\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/checking-the-prisma-model-attributes-in-postgresql-using-pgadmin.webp\" alt=\"checking the prisma model attributes in postgresql using pgadmin\" class=\"wp-image-5366\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/checking-the-prisma-model-attributes-in-postgresql-using-pgadmin.webp 1005w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/checking-the-prisma-model-attributes-in-postgresql-using-pgadmin-300x205.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/checking-the-prisma-model-attributes-in-postgresql-using-pgadmin-768x526.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/checking-the-prisma-model-attributes-in-postgresql-using-pgadmin-100x68.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/checking-the-prisma-model-attributes-in-postgresql-using-pgadmin-657x450.webp 657w\" sizes=\"auto, (max-width: 1005px) 100vw, 1005px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Creating Schemas with Zod<\/h2>\n\n\n\n<p>By default, tRPC has support for schema 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>. Also, it gives you the freedom to use your custom validators.<\/p>\n\n\n\n<p>Run this command to install Zod:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add zod\n<\/code>\n<\/pre>\n\n\n\n<p>Next, create a <code>packages\/server\/src\/schemas\/user.schema.ts<\/code> file with the following schema definitions.<\/p>\n\n\n\n<p><strong>packages\/server\/src\/schema\/user.schema.ts<\/strong><\/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<h2 class=\"wp-block-heading\">Creating Functions to Sign and Verify JWTs<\/h2>\n\n\n\n<pre class=\"line-numbers language-ts\"><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\"><p>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><\/blockquote>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>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><\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Generating the JWT Private and Public Keys<\/h3>\n\n\n\n<p>Follow these steps to generate the private and public keys needed to sign and verify the JWT Tokens.<\/p>\n\n\n\n<p>Alternatively, you can use the ones I provided in the <code>packages\/server\/.env<\/code> file.<\/p>\n\n\n\n<p><strong>Step 1:<\/strong> Go to the <a href=\"http:\/\/travistidwell.com\/jsencrypt\/demo\/\" target=\"_blank\" rel=\"noreferrer noopener\">Private and Public Keys Generation Site<\/a>, and click the <strong>&#8220;Generate New Keys&#8221;<\/strong> button to create the private and public keys.<\/p>\n\n\n\n<p><strong>Step 2:<\/strong> Copy the private key and visit the <a href=\"https:\/\/www.base64encode.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Base64 Encoding website<\/a> to encode it in <strong>Base64<\/strong>.<\/p>\n\n\n\n<p><strong>Step 3:<\/strong> Copy the base64 encoded string 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> Go back to the <a href=\"http:\/\/travistidwell.com\/jsencrypt\/demo\/\" target=\"_blank\" rel=\"noreferrer noopener\">Private and Public Keys Generation Site<\/a> and copy the corresponding public key before encoding it in base64.<\/p>\n\n\n\n<p>Add 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 above steps for the refresh token private and public keys.<\/p>\n\n\n\n<p>In the end, the <code>packages\/server\/.env<\/code> should look somewhat like this:<\/p>\n\n\n\n<p><strong>packages\/server\/.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nNODE_ENV=development\n\nORIGIN=http:\/\/127.0.0.1:3000\n\nDATABASE_URL=\"postgresql:\/\/postgres:password123@localhost:6500\/trpc_prisma?schema=public\"\n\nACCESS_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT1FJQkFBSkFVTWhaQjNucFJ6OEdrc0tneVZJcEZHMkJqZldHdElMWGNLUVFGMHZGbVZvOVVFcDhyOEVmCnI5T204azVTaXgrSi8rbXc0d08xVUlGb25rQTJFWnl6THdJREFRQUJBa0FDcVViOWp3K1hVRVU0S244L2dweGwKMXVHd3VvandnMnJ6aEFRZnNGaFhIKzlyQ1NWTmxNaEk0UWh4bWt3bHI2Y0NhUnFMUGg0Vk5lN3hKRDloWU5XcApBaUVBbXJ4TENiQXJJWDIvYkFETU1JdXljWFZKMnAwUk91S3FQOVBVeTB6ZG0zc0NJUUNGcGs5VDJKQ3NUVEhWCjErMWFVbk9JOFE3eUdNK1VQVGt6a200emNHcE8zUUloQUloOEU3Z2M4ejVjVzQ5WmVNSk5SbjI3VmdTRnpKL2oKTlFhTnc4SDdML0duQWlCTS9lUFJEMzg0WXpnRVV1SGZHSVNLTFNSSS8xWUZ0Y2RRR0ZqM3RSam8yUUlnS2t6ZwpVWFkwWjJRR1dqblFTdzdJRThaSDZuTHcydFUrci9LR0NZRzVKN3M9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t\nACCESS_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZzd0RRWUpLb1pJaHZjTkFRRUJCUUFEU2dBd1J3SkFVTWhaQjNucFJ6OEdrc0tneVZJcEZHMkJqZldHdElMWApjS1FRRjB2Rm1WbzlVRXA4cjhFZnI5T204azVTaXgrSi8rbXc0d08xVUlGb25rQTJFWnl6THdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t\n\nREFRESH_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT1FJQkFBSkFZOHRTUEZXMTk3bWgwcitCWUdLVTA4OFRPcDkrT2FObVNWQ1lMMTFhb05ZeEY1TSs1d0NSCnNDTnAxVEdHNW5zb215NW9QRitLajFsOGhjbmtUSUU2SndJREFRQUJBa0FVN2dLc1ZzbVlVQjJKWnRMS2xVSmoKZmUycGdPUG5VTWJXSDRvYmZQZlIvWGNteTdONkQyVXVQcnJ0MkdQVUpnNVJ4SG5NbVFpaDJkNHUwY3pqRDhpcApBaUVBcDFNaUtvY1BEWDJDU0lGN3c5SzVGWHlqMjIzQXJQcVJoUzNtL1dkVzVlVUNJUUNZcmxyeXRJOFkvODIzCkQ1ZTFHVExnbDlTcXN1UWdvaGF4ZCtKaXludGZHd0lnQ2xlK0xlakpTbWt1cTNLdGhzNDR1SlpLdnA2TElXWWYKcHA3T3YyMHExdTBDSVFDSy9lYWpuZ1hLLzB3NXcwTWJSUVpRK1VkTDRqRFZHRm5LVTFYUEUzOStVd0lnSEdLWgpjcDd2K3VyeG5kU05GK25MVEpZRG9abkMrKytteXRMaCtSUmU4dVU9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t\nREFRESH_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZzd0RRWUpLb1pJaHZjTkFRRUJCUUFEU2dBd1J3SkFZOHRTUEZXMTk3bWgwcitCWUdLVTA4OFRPcDkrT2FObQpTVkNZTDExYW9OWXhGNU0rNXdDUnNDTnAxVEdHNW5zb215NW9QRitLajFsOGhjbmtUSUU2SndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t\n<\/code>\n<\/pre>\n\n\n\n<p>Next, let&#8217;s add the variables to the <code>packages\/server\/src\/config\/default.ts<\/code> file to help us provide the TypeScript types for the environment variables.<\/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  redisCacheExpiresIn: number;\n} = {\n  port: 8000,\n  accessTokenExpiresIn: 15,\n  refreshTokenExpiresIn: 60,\n  redisCacheExpiresIn: 60,\n  origin: 'http:\/\/localhost:3000',\n\n  dbUri: process.env.DATABASE_URL 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 the above configurations, let&#8217;s create a <code>packages\/server\/src\/utils\/jwt.ts<\/code> file and add these two functions to sign and verify the JSON Web Tokens.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Signing the JWT Tokens<\/h3>\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<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Verifying the JWT Tokens<\/h3>\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>\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<h2 class=\"wp-block-heading\">Creating the Database Services with Prisma<\/h2>\n\n\n\n<p>One of the best practices of RESTful API architecture is to separate the business and application logic.<\/p>\n\n\n\n<p>In layman&#8217;s terms, you should create services to interact with the database. That means the controllers should not be allowed to communicate directly with the database.<\/p>\n\n\n\n<p>The rule is to push most of the business logic to the models and services, leaving us with thinner controllers and larger models.<\/p>\n\n\n\n<p>Now let&#8217;s create a <code>packages\/server\/src\/services\/user.service.ts<\/code> file with the following services.<\/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 { Prisma, User } from '@prisma\/client';\nimport customConfig from '..\/config\/default';\nimport redisClient from '..\/utils\/connectRedis';\nimport { signJwt } from '..\/utils\/jwt';\nimport { prisma } from '..\/utils\/prisma';\n\nexport const createUser = async (input: Prisma.UserCreateInput) =&gt; {\n  return (await prisma.user.create({\n    data: input,\n  })) as User;\n};\n\nexport const findUser = async (\n  where: Partial&lt;Prisma.UserCreateInput&gt;,\n  select?: Prisma.UserSelect\n) =&gt; {\n  return (await prisma.user.findFirst({\n    where,\n    select,\n  })) as User;\n};\n\nexport const findUniqueUser = async (\n  where: Prisma.UserWhereUniqueInput,\n  select?: Prisma.UserSelect\n) =&gt; {\n  return (await prisma.user.findUnique({\n    where,\n    select,\n  })) as User;\n};\n\nexport const updateUser = async (\n  where: Partial&lt;Prisma.UserWhereUniqueInput&gt;,\n  data: Prisma.UserUpdateInput,\n  select?: Prisma.UserSelect\n) =&gt; {\n  return (await prisma.user.update({ where, data, select })) as User;\n};\n\nexport const signTokens = async (user: Prisma.UserCreateInput) =&gt; {\n  \/\/ 1. Create Session\n  redisClient.set(`${user.id}`, JSON.stringify(user), {\n    EX: customConfig.redisCacheExpiresIn * 60,\n  });\n\n  \/\/ 2. Create Access and Refresh tokens\n  const access_token = signJwt({ sub: user.id }, 'accessTokenPrivateKey', {\n    expiresIn: `${customConfig.accessTokenExpiresIn}m`,\n  });\n\n  const refresh_token = signJwt({ sub: user.id }, 'refreshTokenPrivateKey', {\n    expiresIn: `${customConfig.refreshTokenExpiresIn}m`,\n  });\n\n  return { access_token, refresh_token };\n};\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the Authentication Handlers<\/h2>\n\n\n\n<p>We are now ready to create the authentication handlers to:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Create a new user<\/li><li>Request a new access token<\/li><li>Sign the user into the account<\/li><li>Log the user out from the account<\/li><\/ol>\n\n\n\n<p>To begin, let&#8217;s define the cookie options. Create a <code>packages\/server\/src\/controllers\/auth.controller.ts<\/code> file with the following imports and cookie options.<\/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 bcrypt from 'bcryptjs';\nimport { CookieOptions } from 'express';\nimport { Context } from '..\/app';\nimport customConfig from '..\/config\/default';\nimport { CreateUserInput, LoginUserInput } from '..\/schema\/user.schema';\nimport {\n  createUser,\n  findUniqueUser,\n  findUser,\n  signTokens,\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: CookieOptions = {\n  ...cookieOptions,\n  expires: new Date(Date.now() + customConfig.accessTokenExpiresIn * 60 * 1000),\n};\n\nconst refreshTokenCookieOptions: CookieOptions = {\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\">How to Register a New User<\/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\/\/ [...] Register User Handler\nexport const registerHandler = async ({\n  input,\n}: {\n  input: CreateUserInput;\n}) =&gt; {\n  try {\n    const hashedPassword = await bcrypt.hash(input.password, 12);\n    const user = await createUser({\n      email: input.email.toLowerCase(),\n      name: input.name,\n      password: hashedPassword,\n      photo: input.photo,\n      provider: 'local',\n    });\n\n    return {\n      status: 'success',\n      data: {\n        user,\n      },\n    };\n  } catch (err: any) {\n    if (err instanceof Prisma.PrismaClientKnownRequestError) {\n      if (err.code === 'P2002') {\n        throw new TRPCError({\n          code: 'CONFLICT',\n          message: 'Email already exists',\n        });\n      }\n    }\n    throw err;\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<p>Let&#8217;s evaluate the above code:<\/p>\n\n\n\n<p>First, we hashed the plain-text password provided by the user and called the <code>createUser()<\/code> service to add the user to the PostgreSQL database.<\/p>\n\n\n\n<p>Also, since we added a unique constraint to the email column, Prisma will return an error with a &#8220;<strong>P2002<\/strong>&#8221; code indicating that the email already exists in the PostgreSQL database.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How to Sign in the User<\/h3>\n\n\n\n<p>Now that we are able to create a user, let&#8217;s define the <strong>tRPC<\/strong> handler to sign in the registered user.<\/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\/\/ [...] Register User Handler\n\n\/\/ [...] Login User Handler\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.toLowerCase() });\n\n    \/\/ Check if user exist and password is correct\n    if (!user || !(await bcrypt.compare(input.password, user.password))) {\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 signTokens(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>Quite a lot happening in the above, let&#8217;s break it down:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>First, we evoked the <code>findUser()<\/code> service to check if that email exists in the PostgreSQL database.<\/li><li>Next, we validated the plain-text password with the hashed one in the database.<\/li><li>Lastly, we generated the access and refresh tokens and sent them to the <strong>tRPC<\/strong> client as <strong>HTTPOnly<\/strong> cookies.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">How to Refresh the Access Token<\/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\/\/ [...] Register User Handler\n\n\/\/ [...] Login User Handler\n\n\/\/ [...] Refresh Access Token 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 findUniqueUser({ id: 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\">How to SignOut the User<\/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\/\/ [...] Register User Handler\n\n\/\/ [...] Login User Handler\n\n\/\/ [...] Refresh Access Token Handler\n\n\/\/ [...] Logout User 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};\n\nexport const logoutHandler = async ({ ctx }: { ctx: Context }) =&gt; {\n  try {\n    const user = ctx.user;\n    await redisClient.del(user?.id as string);\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 Handlers<\/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 bcrypt from 'bcryptjs';\nimport { CookieOptions } from 'express';\nimport { Context } from '..\/app';\nimport customConfig from '..\/config\/default';\nimport { CreateUserInput, LoginUserInput } from '..\/schema\/user.schema';\nimport {\n  createUser,\n  findUniqueUser,\n  findUser,\n  signTokens,\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: CookieOptions = {\n  ...cookieOptions,\n  expires: new Date(Date.now() + customConfig.accessTokenExpiresIn * 60 * 1000),\n};\n\nconst refreshTokenCookieOptions: CookieOptions = {\n  ...cookieOptions,\n  expires: new Date(\n    Date.now() + customConfig.refreshTokenExpiresIn * 60 * 1000\n  ),\n};\n\n\/\/ [...] Register User Handler\nexport const registerHandler = async ({\n  input,\n}: {\n  input: CreateUserInput;\n}) =&gt; {\n  try {\n    const hashedPassword = await bcrypt.hash(input.password, 12);\n    const user = await createUser({\n      email: input.email.toLowerCase(),\n      name: input.name,\n      password: hashedPassword,\n      photo: input.photo,\n      provider: 'local',\n    });\n\n    return {\n      status: 'success',\n      data: {\n        user,\n      },\n    };\n  } catch (err: any) {\n   if (err instanceof Prisma.PrismaClientKnownRequestError) {\n      if (err.code === 'P2002') {\n        throw new TRPCError({\n          code: 'CONFLICT',\n          message: 'Email already exists',\n        });\n      }\n    }\n    throw err;\n  }\n};\n\n\/\/ [...] Login User Handler\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.toLowerCase() });\n\n    \/\/ Check if user exist and password is correct\n    if (!user || !(await bcrypt.compare(input.password, user.password))) {\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 signTokens(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 Access Token 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 findUniqueUser({ id: 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 User 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};\n\nexport const logoutHandler = async ({ ctx }: { ctx: Context }) =&gt; {\n  try {\n    const user = ctx.user;\n    await redisClient.del(user?.id as string);\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\">Creating a User Handler<\/h2>\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 type { 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 Guard<\/h2>\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 { findUniqueUser } from '..\/services\/user.service';\nimport { Request, Response } from 'express';\nimport redisClient from '..\/utils\/connectRedis';\nimport { verifyJwt } from '..\/utils\/jwt';\n\nexport const deserializeUser = async ({\n  req,\n  res,\n}: {\n  req: Request;\n  res: Response;\n}) =&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 findUniqueUser({ id: JSON.parse(session).id });\n\n    if (!user) {\n      return notAuthenticated;\n    }\n\n    return {\n      req,\n      res,\n      user,\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\">Connecting the Routers to the App<\/h2>\n\n\n\n<p>Before adding the <strong>tRPC<\/strong> endpoints, install the following dependencies:<\/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\"><li><code><a href=\"https:\/\/www.npmjs.com\/package\/cookie-parser\" target=\"_blank\" rel=\"noreferrer noopener\">cookie-parser<\/a><\/code> &#8211; for parsing cookies in the request headers<\/li><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<\/li><\/ul>\n\n\n\n<p>Now open the <code>packages\/server\/src\/app.ts<\/code> file and add the following <strong>tRPC<\/strong> endpoints.<\/p>\n\n\n\n<p>In addition, add the cookie parser to the middleware stack to enable Express to parse the cookies.<\/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 { inferAsyncReturnType, initTRPC, TRPCError } from &quot;@trpc\/server&quot;;\nimport * as trpcExpress from &quot;@trpc\/server\/adapters\/express&quot;;\nimport redisClient from &quot;.\/utils\/connectRedis&quot;;\nimport customConfig from &quot;.\/config\/default&quot;;\nimport connectDB from &quot;.\/utils\/prisma&quot;;\nimport { deserializeUser } from &quot;.\/middleware\/deserializeUser&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 cookieParser from &quot;cookie-parser&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 = await redisClient.get(&quot;tRPC&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, &quot;http:\/\/localhost:3000&quot;],\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>Congratulations 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, PostgreSQL, Prisma, Docker-compose, and Redis.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">tRPC, Prisma, PostgreSQL, &amp; Express Source Code<\/h2>\n\n\n\n<p>Check out the:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/github.com\/wpcodevo\/trpc-react-node-prisma\/tree\/trpc-prisma-express-auth\" target=\"_blank\" rel=\"noreferrer noopener\">tRPC Node, Prisma, Express, and PostgreSQL API<\/a><\/li><li><a href=\"https:\/\/github.com\/wpcodevo\/trpc-react-node-prisma\/tree\/trpc-prisma-react-query-auth\" target=\"_blank\" rel=\"noreferrer noopener\">Complete React, PostgreSQL, Prisma, and Node tRPC Source Code<\/a><\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>This article will teach you how to add JSON Web Token (JWT) Authentication to a tRPC API built with PostgreSQL, Prisma, Express, Node.js, and Redis&#8230;.<\/p>\n","protected":false},"author":1,"featured_media":5450,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[47,54],"tags":[42,56,55],"class_list":["post-5380","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-nodejs","category-react","tag-nodejs-api","tag-react","tag-reactjs"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/5380","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=5380"}],"version-history":[{"count":0,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/5380\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/5450"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=5380"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=5380"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=5380"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}