{"id":4461,"date":"2022-07-11T16:52:03","date_gmt":"2022-07-11T16:52:03","guid":{"rendered":"https:\/\/codevoweb.com\/?p=4461"},"modified":"2023-05-06T08:26:20","modified_gmt":"2023-05-06T08:26:20","slug":"fullstack-trpc-crud-application-with-nodejs-and-reactjs","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/fullstack-trpc-crud-application-with-nodejs-and-reactjs\/","title":{"rendered":"Build Full-Stack tRPC CRUD Application with Node.js, and React.js"},"content":{"rendered":"\n<p><a href=\"https:\/\/trpc.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">tRPC<\/a> popularly known as <a href=\"https:\/\/www.npmjs.com\/package\/create-t3-app\" target=\"_blank\" rel=\"noreferrer noopener\">t3-stack<\/a> is a toolkit for building and consuming end-to-end typesafe APIs without depending on defined schemas or extra libraries for code generation. <\/p>\n\n\n\n<p>This article will teach you how to build a  full-stack <strong>tRPC<\/strong> CRUD app to perform the <strong>Create<\/strong>\/<strong>Update<\/strong>\/<strong>Get<\/strong>\/<strong>Delete<\/strong> operations with React.js, Express, and Node.js using a monolithic repository aka <strong>monorepo<\/strong>.<\/p>\n\n\n\n<p>tRPC 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\n\n\n<li><a href=\"\/fullstack-trpc-crud-application-with-nodejs-and-reactjs\">Build Full-Stack tRPC CRUD Application with Node.js, and React.js<\/a><\/li>\n<\/ol>\n\n\n\n<p>Read more 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-Full-Stack-tRPC-CRUD-Application-with-Node.js-and-React.js.webp\" alt=\"Build Full-Stack tRPC CRUD Application with Node.js, and React.js\" class=\"wp-image-4724\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-Full-Stack-tRPC-CRUD-Application-with-Node.js-and-React.js.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-Full-Stack-tRPC-CRUD-Application-with-Node.js-and-React.js-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-Full-Stack-tRPC-CRUD-Application-with-Node.js-and-React.js-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-Full-Stack-tRPC-CRUD-Application-with-Node.js-and-React.js-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/Build-Full-Stack-tRPC-CRUD-Application-with-Node.js-and-React.js-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_d6cb76-15 .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_d6cb76-15 .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_d6cb76-15 .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_d6cb76-15 .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_d6cb76-15 .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_d6cb76-15 .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_d6cb76-15 .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\">What is tRPC?<\/h2>\n\n\n\n<p><a href=\"https:\/\/trpc.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">tRPC<\/a> is a library introduced to solve the complexities of GraphQL and to streamline the process of building type-safety full-stack applications with TypeScript.<\/p>\n\n\n\n<p>tRPC is a lot simpler and couples your server and client more tightly together by allowing you to easily share the TypeScript types between them.<\/p>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-107\"><\/span>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before we start, you should:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>be comfortable with JavaScript, TypeScript, and React.js<\/li>\n\n\n\n<li>be comfortable with CSS and tailwindCss<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">React Query, tRPC Client and Server Overview<\/h2>\n\n\n\n<p>We will build a React.js, tailwindCss, and TypeScript client with React Query and tRPC Client to make CRUD operations against a tRPC API.<\/p>\n\n\n\n<p>Below are the endpoints of the tRPC CRUD API:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>ROUTE<\/th><th>DESCRIPTION<\/th><\/tr><\/thead><tbody><tr><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/trpc\/posts.getPosts<\/mark><\/td><td>Retrieve all posts<\/td><\/tr><tr><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/trpc\/posts.create<\/mark><\/td><td>Create new post<\/td><\/tr><tr><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/trpc\/posts.getPost<\/mark><\/td><td>Get a single post<\/td><\/tr><tr><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/trpc\/posts.update<\/mark><\/td><td>Update a post<\/td><\/tr><tr><td><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">\/api\/trpc\/posts.delete<\/mark><\/td><td>Delete a post<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>-A <strong>tRPC<\/strong> React Query <strong>GET<\/strong> request is made on the homepage to retrieve all posts<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"924\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-fetch-all-post-query_1-1024x924.png\" alt=\"\" class=\"wp-image-4694\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-fetch-all-post-query_1-1024x924.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-fetch-all-post-query_1-300x271.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-fetch-all-post-query_1-768x693.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-fetch-all-post-query_1-100x90.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-fetch-all-post-query_1-499x450.png 499w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-fetch-all-post-query_1.png 1197w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-To add a new post to the database, click on the &#8220;<strong>Create Post<\/strong>&#8221; button on the navigation menu to display the create post popup.<\/p>\n\n\n\n<p>Next, provide the required information and make a <strong>tRPC<\/strong> POST request to the <strong>tRPC<\/strong> server for the post to be added to the database.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"928\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-create-post-mutation-1024x928.png\" alt=\"tRPC fullstack crud app create post mutation\" class=\"wp-image-4697\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-create-post-mutation-1024x928.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-create-post-mutation-300x272.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-create-post-mutation-768x696.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-create-post-mutation-100x91.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-create-post-mutation-497x450.png 497w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-create-post-mutation.png 1192w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-To update a post, click on the three dots adjacent to the author&#8217;s name and then click the edit button to display the update post popup.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"925\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-edit-post-overview-1024x925.png\" alt=\"tRPC fullstack crud app edit post overview\" class=\"wp-image-4695\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-edit-post-overview-1024x925.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-edit-post-overview-300x271.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-edit-post-overview-768x694.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-edit-post-overview-100x90.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-edit-post-overview-498x450.png 498w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-edit-post-overview.png 1195w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Here the fields will be automatically filled when the popup mounts. Edit the fields you want and make a <strong>tRPC<\/strong> POST request to update the post on the tRPC server.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"925\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-update-post-mutation-1024x925.png\" alt=\"tRPC fullstack crud app update post mutation\" class=\"wp-image-4693\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-update-post-mutation-1024x925.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-update-post-mutation-300x271.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-update-post-mutation-768x693.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-update-post-mutation-100x90.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-update-post-mutation-498x450.png 498w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-update-post-mutation.png 1195w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>-To remove a post from the database, click on the three dots again and then click the delete button. You will be prompted to confirm your action before a <strong>tRPC<\/strong> POST request is made to the tRPC server to delete the post from the database.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"923\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-delete-post-mutation-1024x923.png\" alt=\"tRPC fullstack crud app delete post mutation\" class=\"wp-image-4696\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-delete-post-mutation-1024x923.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-delete-post-mutation-300x270.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-delete-post-mutation-768x692.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-delete-post-mutation-100x90.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-delete-post-mutation-499x450.png 499w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-fullstack-crud-app-delete-post-mutation.png 1198w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Follow these articles to implement the <strong>tRPC<\/strong> server and the authentication aspect before continuing with this tutorial.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/trpc-api-reactjs-nodejs-mongodb-project-setup\">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\">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\">FullStack App tRPC, React.js, MongoDB and  Node.js: JWT Authentication<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">tRPC Client and React Query Project Setup<\/h2>\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\">tRPC Project Setup<\/a> article to set up the <strong>tRPC<\/strong> client and server with React, Node.js, Express, MongoDB, and tailwindCss before continuing with this tutorial.<\/p>\n<\/blockquote>\n\n\n\n<p>After the project setup, change the directory into the client folder <code>cd packages\/client<\/code> and run the following code to install the <strong>AppRouter<\/strong> type we exported from the tRPC server.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add server@1.0.0\n<\/code><\/pre>\n\n\n\n<p>Where:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>server<\/code> &#8211; is the name used in the <code>packages\/server\/package.json<\/code> file.<\/li>\n\n\n\n<li><code>@1.0.0<\/code> &#8211; is the version of the <code>packages\/server\/package.json<\/code> file.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the TypeScript Types<\/h2>\n\n\n\n<p>Let&#8217;s create TypeScript types to help us type the tRPC API responses. So, navigate into the <code>packages\/client\/src<\/code> directory and create a <strong>lib<\/strong> folder. Within the <strong>lib<\/strong> folder, create a <strong>types.ts<\/strong> file and add the following code.<\/p>\n\n\n\n<p><strong>packages\/client\/src\/lib\/types.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nexport interface IUser {\n  name: string;\n  email: string;\n  role: string;\n  photo: string;\n  _id: string;\n  id: string;\n  createdAt: string;\n  updatedAt: string;\n  __v: number;\n}\n\nexport type IPost = {\n  _id: string;\n  id: string;\n  title: string;\n  content: string;\n  category: string;\n  image: string;\n  createdAt: string;\n  updatedAt: string;\n  user: {\n    email: string;\n    name: string;\n    photo: string;\n  };\n};\n\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Create a Modal Component with TailwindCSS<\/h2>\n\n\n\n<p>Here, let&#8217;s create a reusable modal component with React Portals. We could have used z-Index and some lines of CSS to render the popup above all the elements in the DOM hierarchy but this approach disrupts the parent-child relationship in the DOM hierarchy.<\/p>\n\n\n\n<p>Create a <strong>modals<\/strong> folder in the <code>packages\/client\/src\/components<\/code> directory and create a <code>post.modal.tsx<\/code> file in the <strong>modals<\/strong> folder. After that, open the <strong>post.modal.tsx<\/strong> file and add the following TSX code.<\/p>\n\n\n\n<p><strong>packages\/client\/src\/components\/modals\/post.modal.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport ReactDom from 'react-dom';\nimport React, { FC } from 'react';\n\ntype IPostModal = {\n  openPostModal: boolean;\n  setOpenPostModal: (openPostModal: boolean) =&gt; void;\n  children: React.ReactNode;\n};\n\nconst PostModal: FC&lt;IPostModal&gt; = ({\n  openPostModal,\n  setOpenPostModal,\n  children,\n}) =&gt; {\n  if (!openPostModal) return null;\n  return ReactDom.createPortal(\n    &lt;&gt;\n      &lt;div\n        className='fixed inset-0 bg-[rgba(0,0,0,.5)] z-[1000]'\n        onClick={() =&gt; setOpenPostModal(false)}\n      &gt;&lt;\/div&gt;\n      &lt;div className='max-w-lg w-full rounded-md fixed top-[15%] left-1\/2 -translate-x-1\/2 bg-white z-[1001] p-6'&gt;\n        {children}\n      &lt;\/div&gt;\n    &lt;\/&gt;,\n    document.getElementById('post-modal') as HTMLElement\n  );\n};\n\nexport default PostModal;\n<\/code><\/pre>\n\n\n\n<p>Next, add the React Portal element as a sibling element to the root div in the<code>packages\/client\/public\/index.html<\/code> file. React will render the content of the modal popup in the portal when the modal is active.<\/p>\n\n\n\n<pre class=\"line-numbers language-html\"><code>\n&lt;div id=\"post-modal\"&gt;&lt;\/div&gt;\n<\/code><\/pre>\n\n\n\n<p>After adding the React portal element, you should end up with an HTML code that looks somewhat like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"611\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-edit-the-html-file-1024x611.png\" alt=\"tRPC edit the html file\" class=\"wp-image-4712\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-edit-the-html-file-1024x611.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-edit-the-html-file-300x179.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-edit-the-html-file-768x458.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-edit-the-html-file-100x60.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-edit-the-html-file-700x418.png 700w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/07\/tRPC-edit-the-html-file.png 1201w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">tRPC Client and React Query DELETE Request<\/h2>\n\n\n\n<p>Now change the directory into the <code>packages\/client<\/code> folder in your terminal and run this command to install the <code><a href=\"https:\/\/www.npmjs.com\/package\/date-fns\" target=\"_blank\" rel=\"noreferrer noopener\">date-fns<\/a><\/code> library. This package will help us format the <strong>createdAt<\/strong> and <strong>updatedAt<\/strong> timestamps returned by the tRPC server.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add date-fns\n<\/code><\/pre>\n\n\n\n<p><code>date-fns<\/code> &#8211; is a library that provides utility functions for manipulating JavaScript dates in the browser and Node.js. <\/p>\n\n\n\n<p>Let&#8217;s create a React component to implement the <strong>DELETE<\/strong> operation. When the <code>deletePost<\/code> mutation hook is triggered, the <code>deletePost<\/code> tRPC procedure will be evoked to delete the record that matches the ID. <\/p>\n\n\n\n<p><strong>packages\/client\/src\/components\/posts\/post.component.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport React, { FC, useEffect, useState } from \"react\";\nimport { format, parseISO } from \"date-fns\";\nimport { twMerge } from \"tailwind-merge\";\nimport { IPost } from \"..\/..\/lib\/types\";\nimport { toast } from \"react-toastify\";\nimport useStore from \"..\/..\/store\";\nimport PostModal from \"..\/modals\/post.modal\";\nimport UpdatePost from \".\/update.post\";\nimport { trpc } from \"..\/..\/trpc\";\nimport { useQueryClient } from \"@tanstack\/react-query\";\n\ntype PostItemProps = {\n  post: IPost;\n};\n\nconst PostItem: FC&lt;PostItemProps&gt; = ({ post }) =&gt; {\n  const [openMenu, setOpenMenu] = useState(false);\n  const [openPostModal, setOpenPostModal] = useState(false);\n  const store = useStore();\n  const queryClient = useQueryClient();\n  const { isLoading, mutate: deletePost } = trpc.deletePost.useMutation({\n    onSuccess(data) {\n      store.setPageLoading(false);\n      queryClient.refetchQueries([[\"getPosts\"]]);\n      toast(\"Post deleted successfully\", {\n        type: \"success\",\n        position: \"top-right\",\n      });\n    },\n    onError(error: any) {\n      store.setPageLoading(false);\n      error.response.errors.forEach((err: any) =&gt; {\n        toast(err.message, {\n          type: \"error\",\n          position: \"top-right\",\n        });\n      });\n    },\n  });\n\n  useEffect(() =&gt; {\n    if (isLoading) {\n      store.setPageLoading(true);\n    }\n    \/\/ eslint-disable-next-line react-hooks\/exhaustive-deps\n  }, [isLoading]);\n\n  const toggleMenu = () =&gt; {\n    setOpenMenu(!openMenu);\n  };\n\n  const onDeleteHandler = (id: string) =&gt; {\n    toggleMenu();\n    if (window.confirm(\"Are you sure\")) {\n      deletePost({ postId: id });\n    }\n  };\n  return (\n    &lt;&gt;\n      &lt;div\n        className=\"rounded-md shadow-md bg-white\"\n        onClick={() =&gt; toggleMenu()}\n      &gt;\n        &lt;div className=\"mx-2 mt-2 overflow-hidden rounded-md\"&gt;\n          &lt;img\n            src={post.image}\n            alt={post.title}\n            className=\"object-fill w-full h-full\"\n          \/&gt;\n        &lt;\/div&gt;\n        &lt;div className=\"p-4\"&gt;\n          &lt;h5 className=\"font-semibold text-xl text-[#4d4d4d] mb-4\"&gt;\n            {post.title.length &gt; 25\n              ? post.title.substring(0, 25) + \"...\"\n              : post.title}\n          &lt;\/h5&gt;\n          &lt;div className=\"flex items-center mt-4\"&gt;\n            &lt;p className=\"p-1 rounded-sm mr-4 bg-[#dad8d8]\"&gt;{post.category}&lt;\/p&gt;\n            &lt;p className=\"text-[#ffa238]\"&gt;\n              {format(parseISO(post.createdAt), \"PPP\")}\n            &lt;\/p&gt;\n          &lt;\/div&gt;\n        &lt;\/div&gt;\n        &lt;div className=\"flex justify-between items-center px-4 pb-4\"&gt;\n          &lt;div className=\"flex items-center\"&gt;\n            &lt;div className=\"w-12 h-12 rounded-full overflow-hidden\"&gt;\n              &lt;img\n                src={post.user.photo}\n                alt={post.user.name}\n                className=\"object-cover w-full h-full\"\n              \/&gt;\n            &lt;\/div&gt;\n            &lt;p className=\"ml-4 text-sm font-semibold\"&gt;{post.user.name}&lt;\/p&gt;\n          &lt;\/div&gt;\n          &lt;div className=\"relative\"&gt;\n            &lt;div\n              className=\"text-3xl text-[#4d4d4d] cursor-pointer p-3\"\n              onClick={toggleMenu}\n            &gt;\n              &lt;i className=\"bx bx-dots-horizontal-rounded\"&gt;&lt;\/i&gt;\n            &lt;\/div&gt;\n            &lt;ul\n              className={twMerge(\n                `absolute bottom-5 -right-1 z-50 py-2 rounded-sm bg-white shadow-lg transition ease-out duration-300 invisible`,\n                `${openMenu ? \"visible\" : \"invisible\"}`\n              )}\n            &gt;\n              &lt;li\n                className=\"w-24 h-7 py-3 px-2 hover:bg-[#f5f5f5] flex items-center gap-2 cursor-pointer transition ease-in duration-300\"\n                onClick={() =&gt; {\n                  setOpenPostModal(true);\n                  toggleMenu();\n                }}\n              &gt;\n                &lt;i className=\"bx bx-edit-alt\"&gt;&lt;\/i&gt; &lt;span&gt;Edit&lt;\/span&gt;\n              &lt;\/li&gt;\n              &lt;li\n                className=\"w-24 h-7 py-3 px-2 hover:bg-[#f5f5f5] flex items-center gap-2 cursor-pointer transition ease-in duration-300\"\n                onClick={() =&gt; onDeleteHandler(post._id)}\n              &gt;\n                &lt;i className=\"bx bx-trash\"&gt;&lt;\/i&gt; &lt;span&gt;Delete&lt;\/span&gt;\n              &lt;\/li&gt;\n            &lt;\/ul&gt;\n          &lt;\/div&gt;\n        &lt;\/div&gt;\n      &lt;\/div&gt;\n      &lt;PostModal\n        openPostModal={openPostModal}\n        setOpenPostModal={setOpenPostModal}\n      &gt;\n        &lt;UpdatePost post={post} setOpenPostModal={setOpenPostModal} \/&gt;\n      &lt;\/PostModal&gt;\n    &lt;\/&gt;\n  );\n};\n\nexport default PostItem;\n<\/code><\/pre>\n\n\n\n<p>On a successful mutation, a success alert notification message will be displayed and the <code>[[\"getPosts\"]]<\/code> query will be invalidated. This will cause React Query to request the most current data from the tRPC API. Otherwise, an error notification will be displayed to indicate the error message sent by the tRPC API.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">tRPC Client and React Query GET Request<\/h2>\n\n\n\n<p>Here, let&#8217;s create a component to perform the <strong>READ<\/strong> functionality of CRUD. Before that, let&#8217;s create a <strong>Message<\/strong> component that will be displayed when there are no records in the database. So, create a <strong>Message.tsx<\/strong> file in the <code>packages\/client\/src\/components<\/code> folder and add the following TSX code.<\/p>\n\n\n\n<p><strong>packages\/client\/src\/components\/Message.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport React, { FC } from 'react';\n\ntype IMessageProps = {\n  children: React.ReactNode;\n};\nconst Message: FC&lt;IMessageProps&gt; = ({ children }) =&gt; {\n  return (\n    &lt;div\n      className='max-w-3xl mx-auto rounded-lg px-4 py-3 shadow-md bg-teal-100 flex items-center justify-center h-40'\n      role='alert'\n    &gt;\n      &lt;span className='text-teal-500 text-xl font-semibold'&gt;{children}&lt;\/span&gt;\n    &lt;\/div&gt;\n  );\n};\n\nexport default Message;\n<\/code><\/pre>\n\n\n\n<p>With that out of the way, let&#8217;s create the component to fetch a list of records from the tRPC API and display them in the UI. Go into the <code>packages\/client\/src\/pages<\/code> folder and create a <code>home.page.tsx<\/code> file. Then open the newly-created <strong>home.page.tsx<\/strong> file and add the following code.<\/p>\n\n\n\n<p><strong>packages\/client\/src\/pages\/home.page.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport { useEffect } from \"react\";\nimport { useCookies } from \"react-cookie\";\nimport { useNavigate } from \"react-router-dom\";\nimport { toast } from \"react-toastify\";\nimport Message from \"..\/components\/Message\";\nimport PostItem from \"..\/components\/posts\/post.component\";\nimport useStore from \"..\/store\";\nimport { trpc } from \"..\/trpc\";\n\nconst HomePage = () =&gt; {\n  const [cookies] = useCookies([\"logged_in\"]);\n  const store = useStore();\n  const navigate = useNavigate();\n  const { data: posts } = trpc.getPosts.useQuery(\n    { limit: 10, page: 1 },\n    {\n      select: (data) =&gt; data.data.posts,\n      retry: 1,\n      onSuccess: (data) =&gt; {\n        store.setPageLoading(false);\n      },\n      onError(error: any) {\n        store.setPageLoading(false);\n        toast(error.message, {\n          type: \"error\",\n          position: \"top-right\",\n        });\n      },\n    }\n  );\n\n  useEffect(() =&gt; {\n    if (!cookies.logged_in) {\n      navigate(\"\/login\");\n    }\n  }, [cookies.logged_in, navigate]);\n\n  return (\n    &lt;&gt;\n      &lt;section className=\"bg-ct-blue-600 min-h-screen py-12\"&gt;\n        &lt;div&gt;\n          {posts?.length === 0 ? (\n            &lt;Message&gt;There are no posts at the moment&lt;\/Message&gt;\n          ) : (\n            &lt;div className=\"max-w-6xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-5 px-6\"&gt;\n              {posts?.map((post: any) =&gt; (\n                &lt;PostItem key={post._id} post={post} \/&gt;\n              ))}\n            &lt;\/div&gt;\n          )}\n        &lt;\/div&gt;\n      &lt;\/section&gt;\n    &lt;\/&gt;\n  );\n};\n\nexport default HomePage;\n<\/code><\/pre>\n\n\n\n<p>When this component mounts, the <code>getPosts<\/code> query hook will be triggered to fetch the first 10 records from the tRPC API. On successful query, React Query will update the server state with the list of records and React will re-render the DOM to display them in the UI. On error, an alert notification will be displayed to show the error sent by the tRPC API.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">tRPC and React Query CREATE Request<\/h2>\n\n\n\n<p>We are now ready to implement the <strong>CREATE<\/strong> operation of CRUD. This component will contain a form that has a title, category, content, and image fields. The validation schema will be created with Zod and the form validation will be handled by React-Hook-Form.<\/p>\n\n\n\n<p>When the form is submitted and there are no validation errors, the <code>onSubmitHandler()<\/code> function will be evoked by React-Hook-Form which will in turn trigger the <code>createPost<\/code> mutation hook to submit the form data to the tRPC API.<\/p>\n\n\n\n<p>If the tRPC API adds the new record to the database and returns a successful response, a success alert notification will be displayed and the <code>.refetchQueries([[\"getPosts\"]])<\/code> method will be evoked to re-fetch the latest list of records from the tRPC API.<\/p>\n\n\n\n<p><strong>packages\/client\/src\/components\/posts\/create.post.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport { FC, useEffect } from \"react\";\nimport { FormProvider, SubmitHandler, useForm } from \"react-hook-form\";\nimport { twMerge } from \"tailwind-merge\";\nimport { object, string, TypeOf } from \"zod\";\nimport { zodResolver } from \"@hookform\/resolvers\/zod\";\nimport FileUpLoader from \"..\/FileUpload\";\nimport { LoadingButton } from \"..\/LoadingButton\";\nimport TextInput from \"..\/TextInput\";\nimport { toast } from \"react-toastify\";\nimport useStore from \"..\/..\/store\";\nimport { trpc } from \"..\/..\/trpc\";\nimport { useQueryClient } from \"@tanstack\/react-query\";\n\nconst createPostSchema = object({\n  title: string().min(1, \"Title is required\"),\n  category: string().min(1, \"Category is required\"),\n  content: string().min(1, \"Content is required\"),\n  image: string().min(1, \"Image is required\"),\n});\n\ntype CreatePostInput = TypeOf&lt;typeof createPostSchema&gt;;\n\ntype ICreatePostProp = {\n  setOpenPostModal: (openPostModal: boolean) =&gt; void;\n};\n\nconst CreatePost: FC&lt;ICreatePostProp&gt; = ({ setOpenPostModal }) =&gt; {\n  const store = useStore();\n  const queryClient = useQueryClient();\n  const { isLoading, mutate: createPost } = trpc.createPost.useMutation({\n    onSuccess(data) {\n      store.setPageLoading(false);\n      setOpenPostModal(false);\n      queryClient.refetchQueries([[\"getPosts\"]]);\n      toast(\"Post created successfully\", {\n        type: \"success\",\n        position: \"top-right\",\n      });\n    },\n    onError(error: any) {\n      store.setPageLoading(false);\n      setOpenPostModal(false);\n       toast(error.message, {\n          type: \"error\",\n          position: \"top-right\",\n        });\n    },\n  });\n  const methods = useForm&lt;CreatePostInput&gt;({\n    resolver: zodResolver(createPostSchema),\n  });\n\n  const {\n    register,\n    handleSubmit,\n    formState: { errors },\n  } = methods;\n\n  useEffect(() =&gt; {\n    if (isLoading) {\n      store.setPageLoading(true);\n    }\n    \/\/ eslint-disable-next-line react-hooks\/exhaustive-deps\n  }, [isLoading]);\n\n  const onSubmitHandler: SubmitHandler&lt;CreatePostInput&gt; = async (data) =&gt; {\n    createPost(data);\n  };\n  return (\n    &lt;section&gt;\n      &lt;h2 className=\"text-2xl font-semibold mb-4\"&gt;Create Post&lt;\/h2&gt;\n\n      &lt;FormProvider {...methods}&gt;\n        &lt;form className=\"w-full\" onSubmit={handleSubmit(onSubmitHandler)}&gt;\n          &lt;TextInput name=\"title\" label=\"Title\" \/&gt;\n          &lt;TextInput name=\"category\" label=\"Category\" \/&gt;\n          &lt;div className=\"mb-2\"&gt;\n            &lt;label className=\"block text-gray-700 text-lg mb-2\" htmlFor=\"title\"&gt;\n              Content\n            &lt;\/label&gt;\n            &lt;textarea\n              className={twMerge(\n                `appearance-none border border-ct-dark-200 rounded w-full py-3 px-3 text-gray-700 mb-2 leading-tight focus:outline-none`,\n                `${errors.content &amp;&amp; \"border-red-500\"}`\n              )}\n              rows={4}\n              {...register(\"content\")}\n            \/&gt;\n            &lt;p\n              className={twMerge(\n                `text-red-500 text-xs italic mb-2 invisible`,\n                `${errors.content &amp;&amp; \"visible\"}`\n              )}\n            &gt;\n              {errors.content ? errors.content.message : \"\"}\n            &lt;\/p&gt;\n          &lt;\/div&gt;\n          &lt;FileUpLoader name=\"image\" \/&gt;\n          &lt;LoadingButton loading={isLoading} textColor=\"text-ct-blue-600\"&gt;\n            Create Post\n          &lt;\/LoadingButton&gt;\n        &lt;\/form&gt;\n      &lt;\/FormProvider&gt;\n    &lt;\/section&gt;\n  );\n};\n\nexport default CreatePost;\n<\/code><\/pre>\n\n\n\n<p>However, if the mutation ends in an error, an alert notification will be displayed to show the error sent by the tRPC server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">tRPC and React Query UPDATE Request<\/h2>\n\n\n\n<p>Finally, let&#8217;s create a component to perform the <strong>UPDATE<\/strong> operation of CRUD. This component will also contain a form that will have a title, category, content, and image fields. These input fields will be automatically filled with initial values when the component mounts.<\/p>\n\n\n\n<p>When the form is submitted and is valid, React-Hook-Form will call the <code>onSubmitHandler<\/code> function. The <strong>onSubmitHandler<\/strong> function will then call the <strong>updatePost<\/strong> function which will trigger the <code>updatePost<\/code> mutation hook.<\/p>\n\n\n\n<p>The <code>updatePost<\/code> mutation hook will add the form data to the request body and evoke the <strong>updatePost<\/strong> procedure on the tRPC server to update the fields of the record that matches the query. If the mutation results in a success, the <code>.refetchQueries([[\"getPosts\"]])<\/code> method will be evoked to re-fetch the new list of records from the tRPC API.<\/p>\n\n\n\n<p><strong>packages\/client\/src\/components\/posts\/update.post.tsx<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-tsx\"><code>\nimport React, { FC, useEffect } from \"react\";\nimport { FormProvider, SubmitHandler, useForm } from \"react-hook-form\";\nimport { twMerge } from \"tailwind-merge\";\nimport { object, string, TypeOf } from \"zod\";\nimport { zodResolver } from \"@hookform\/resolvers\/zod\";\nimport FileUpLoader from \"..\/FileUpload\";\nimport { LoadingButton } from \"..\/LoadingButton\";\nimport TextInput from \"..\/TextInput\";\nimport { toast } from \"react-toastify\";\nimport useStore from \"..\/..\/store\";\nimport { IPost } from \"..\/..\/lib\/types\";\nimport { trpc } from \"..\/..\/trpc\";\nimport { useQueryClient } from \"@tanstack\/react-query\";\n\ntype IUpdatePostProps = {\n  post: IPost;\n  setOpenPostModal: (openPostModal: boolean) =&gt; void;\n};\n\nconst updatePostSchema = object({\n  title: string().min(1, \"Title is required\"),\n  category: string().min(1, \"Category is required\"),\n  content: string().min(1, \"Content is required\"),\n  image: string().min(1, \"Image is required\"),\n});\n\ntype UpdatePostInput = TypeOf&lt;typeof updatePostSchema&gt;;\n\nconst UpdatePost: FC&lt;IUpdatePostProps&gt; = ({ post, setOpenPostModal }) =&gt; {\n  const queryClient = useQueryClient();\n  const store = useStore();\n  const { isLoading, mutate: updatePost } = trpc.updatePost.useMutation({\n    onSuccess(data) {\n      store.setPageLoading(false);\n      setOpenPostModal(false);\n      queryClient.refetchQueries([[\"getPosts\"]]);\n      toast(\"Post updated successfully\", {\n        type: \"success\",\n        position: \"top-right\",\n      });\n    },\n    onError(error: any) {\n      store.setPageLoading(false);\n      setOpenPostModal(false);\n      toast(error.message, {\n          type: \"error\",\n          position: \"top-right\",\n        });\n    },\n  });\n  const methods = useForm&lt;UpdatePostInput&gt;({\n    resolver: zodResolver(updatePostSchema),\n  });\n\n  const {\n    register,\n    handleSubmit,\n    formState: { errors },\n  } = methods;\n\n  useEffect(() =&gt; {\n    if (isLoading) {\n      store.setPageLoading(true);\n    }\n    \/\/ eslint-disable-next-line react-hooks\/exhaustive-deps\n  }, [isLoading]);\n\n  useEffect(() =&gt; {\n    if (post) {\n      methods.reset(post);\n    }\n    \/\/ eslint-disable-next-line react-hooks\/exhaustive-deps\n  }, []);\n\n  const onSubmitHandler: SubmitHandler&lt;UpdatePostInput&gt; = async (data) =&gt; {\n    updatePost({ body: data, params: { postId: post._id } });\n  };\n  return (\n    &lt;section&gt;\n      &lt;h2 className=\"text-2xl font-semibold mb-4\"&gt;Update Post&lt;\/h2&gt;\n      &lt;FormProvider {...methods}&gt;\n        &lt;form className=\"w-full\" onSubmit={handleSubmit(onSubmitHandler)}&gt;\n          &lt;TextInput name=\"title\" label=\"Title\" \/&gt;\n          &lt;TextInput name=\"category\" label=\"Category\" \/&gt;\n          &lt;div className=\"mb-2\"&gt;\n            &lt;label className=\"block text-gray-700 text-lg mb-2\" htmlFor=\"title\"&gt;\n              Content\n            &lt;\/label&gt;\n            &lt;textarea\n              className={twMerge(\n                `appearance-none border border-ct-dark-200 rounded w-full py-3 px-3 text-gray-700 mb-2 leading-tight focus:outline-none`,\n                `${errors.content &amp;&amp; \"border-red-500\"}`\n              )}\n              rows={4}\n              {...register(\"content\")}\n            \/&gt;\n            &lt;p\n              className={twMerge(\n                `text-red-500 text-xs italic mb-2 invisible`,\n                `${errors.content &amp;&amp; \"visible\"}`\n              )}\n            &gt;\n              {errors.content ? errors.content.message : \"\"}\n            &lt;\/p&gt;\n          &lt;\/div&gt;\n          &lt;FileUpLoader name=\"image\" \/&gt;\n          &lt;LoadingButton loading={isLoading} textColor=\"text-ct-blue-600\"&gt;\n            Update Post\n          &lt;\/LoadingButton&gt;\n        &lt;\/form&gt;\n      &lt;\/FormProvider&gt;\n    &lt;\/section&gt;\n  );\n};\n\nexport default UpdatePost;\n<\/code><\/pre>\n\n\n\n<p>On the other hand, if the mutation results in an error, an alert message will be displayed to indicate the error sent by the tRPC API.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>With this React Query, tRPC Client, tailwindCss, React-Hook-Form, and  Zod example in TypeScript, you&#8217;ve learned how to perform CRUD (<strong>Create<\/strong>, <strong>Read<\/strong>, <strong>Update<\/strong>, and <strong>Delete<\/strong>) operations against a tRPC API.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">tRPC Full-Stack App Source Code<\/h2>\n\n\n\n<p>You can find the <a href=\"https:\/\/github.com\/wpcodevo\/trpc-react-node-mongodb\/tree\/trpc-react-node-crud\" target=\"_blank\" rel=\"noreferrer noopener\">complete source code on my GitHub<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>tRPC popularly known as t3-stack is a toolkit for building and consuming end-to-end typesafe APIs without depending on defined schemas or extra libraries for code&#8230;<\/p>\n","protected":false},"author":1,"featured_media":4724,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[54,47],"tags":[42,56,55],"class_list":["post-4461","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-react","category-nodejs","tag-nodejs-api","tag-react","tag-reactjs"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4461","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=4461"}],"version-history":[{"count":4,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4461\/revisions"}],"predecessor-version":[{"id":11291,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/4461\/revisions\/11291"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/4724"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=4461"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=4461"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=4461"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}