{"id":7485,"date":"2022-09-07T20:19:55","date_gmt":"2022-09-07T20:19:55","guid":{"rendered":"https:\/\/codevoweb.com\/?p=7485"},"modified":"2023-05-05T20:02:41","modified_gmt":"2023-05-05T20:02:41","slug":"complete-grpc-crud-api-with-nodejs-and-express","status":"publish","type":"post","link":"https:\/\/codevoweb.com\/complete-grpc-crud-api-with-nodejs-and-express\/","title":{"rendered":"Build a Complete CRUD gRPC API with Node.js and Express"},"content":{"rendered":"\n<p>Google Remote Procedure Call, also commonly referred to as <strong>gRPC<\/strong> is a modern, open-source, high-performance remote procedure call (<strong>RPC<\/strong>) framework that is frequently used in microservice architectures to connect small independent services in and across data centers efficiently.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><a href=\"https:\/\/www.grpc.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">gRPC<\/a> is a modern open source high performance Remote Procedure Call (<strong>RPC<\/strong>) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.<\/p>\n<cite>https:\/\/www.grpc.io\/<\/cite><\/blockquote>\n\n\n\n<p>What is an <strong>RPC<\/strong> (Remote Procedure Call) framework? <strong>RPC<\/strong> is a distributed computing technique that a program can use to execute a piece of code running on the same or a remote machine. The&nbsp;procedure call is&nbsp;commonly referred to as a&nbsp;<em><strong>function call<\/strong><\/em>&nbsp;or a&nbsp;<em><strong>subroutine call<\/strong><\/em>.<\/p>\n\n\n\n<p>In this guide, you will learn how to build a complete CRUD gRPC API server and client with TypeScript in a Node.js environment that runs on an <a href=\"https:\/\/expressjs.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Expressjs framework<\/a> and uses a <a href=\"https:\/\/www.prisma.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Prisma database ORM<\/a>. We&#8217;ll create five RPC services on the gRPC server that are going to be evoked by the gRPC client to perform the basic CRUD (<strong>Create<\/strong>, <strong>Read<\/strong>, <strong>Update<\/strong>, and <strong>Delete<\/strong>) operations.<\/p>\n\n\n\n<p>To do this, we will create three <code>.proto<\/code> files that will hold the Protobuf definitions to create a new record, update the existing record, and the RPC response.<\/p>\n\n\n\n<p>Next, we&#8217;ll create the <code>services.proto<\/code> file to define the RPC methods or services that will be evoked by the gRPC client.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"\/build-nodejs-grpc-server-and-client-register-and-login-user\">Build a Node.js gRPC Server and Client: Register and Login User<\/a><\/li>\n\n\n\n<li><a href=\"\/nodejs-grpc-api-server-and-client-send-html-emails\">Build Node.js gRPC API Server and Client: Send HTML Emails<\/a><\/li>\n\n\n\n<li><a href=\"\/complete-grpc-crud-api-with-nodejs-and-express\">Build a Complete gRPC CRUD API with Node.js and Express<\/a><\/li>\n<\/ol>\n\n\n\n<p>More practice:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/golang-grpc-server-and-client-signup-user-verify-email\">Build Golang gRPC Server and Client: SignUp User &amp; Verify Email<\/a><\/li>\n\n\n\n<li><a href=\"\/golang-grpc-server-and-client-access-refresh-tokens\">Build Golang gRPC Server and Client: Access &amp; Refresh Tokens<\/a><\/li>\n\n\n\n<li><a href=\"\/crud-grpc-server-api-client-with-golang-and-mongodb\">Build CRUD gRPC Server API &amp; Client with Golang and MongoDB<\/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\/09\/Build-a-Complete-CRUD-gRPC-API-with-Node.js-and-Express.webp\" alt=\"Build a Complete CRUD gRPC API with Node.js and Express\" class=\"wp-image-7605\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/Build-a-Complete-CRUD-gRPC-API-with-Node.js-and-Express.webp 850w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/Build-a-Complete-CRUD-gRPC-API-with-Node.js-and-Express-300x169.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/Build-a-Complete-CRUD-gRPC-API-with-Node.js-and-Express-768x432.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/Build-a-Complete-CRUD-gRPC-API-with-Node.js-and-Express-100x56.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/Build-a-Complete-CRUD-gRPC-API-with-Node.js-and-Express-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_77efb2-a7 .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_77efb2-a7 .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_77efb2-a7 .kb-table-of-contents-title-wrap{color:#ffffff;}.kb-table-of-content-nav.kb-table-of-content-id_77efb2-a7 .kb-table-of-contents-title{color:#ffffff;font-weight:regular;font-style:normal;}.kb-table-of-content-nav.kb-table-of-content-id_77efb2-a7 .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_77efb2-a7 .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_77efb2-a7 .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\">Prerequisites<\/h2>\n\n\n\n<p>Before you begin this tutorial:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Software<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Have a working Node.js development environment. Visit the <a href=\"https:\/\/nodejs.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">official Node.js website<\/a> to download the latest or the LTS (Long Term Support) version.<\/li>\n\n\n\n<li>A basic understanding of <a href=\"https:\/\/www.prisma.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">Prisma<\/a>, and how to use a <a href=\"https:\/\/www.pgadmin.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">PostgreSQL GUI client<\/a> will be beneficial.<\/li>\n\n\n\n<li>Have the latest version of <a href=\"https:\/\/docs.docker.com\/get-docker\/\" target=\"_blank\" rel=\"noreferrer noopener\">Docker<\/a> installed on your system.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">VS Code Extensions<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=mikestead.dotenv\" target=\"_blank\" rel=\"noreferrer noopener\">DotENV<\/a><span style=\"color: initial;\"> &#8211; This extension provides syntax highlighting for the configuration files ending with<\/span> <code>.env<\/code> extension.<\/li>\n\n\n\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=zxh404.vscode-proto3\" target=\"_blank\" rel=\"noreferrer noopener\">Proto3<\/a> &#8211; This extension uses the Protobuf compiler under the hood to provide syntax highlighting, syntax validation, line and block comments, compilation, code snippets, code completion, and code formatting for all files ending with <code>.proto<\/code> extension.<br>You can download and install the right Protocol buffer compiler binary executable for your operating system from the <a href=\"https:\/\/grpc.io\/\" target=\"_blank\" rel=\"noreferrer noopener\">gRPC website<\/a>.<\/li>\n\n\n\n<li><a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=cweijan.vscode-mysql-client2\" target=\"_blank\" rel=\"noreferrer noopener\">MySQL<\/a> &#8211; This VS Code database client will enable you to access and mutate the data stored in any supported database including PostgreSQL.<\/li>\n<\/ul>\n\n\n\n<p>By default, the Protobuf compiler searches for the <code>.proto<\/code> files in the root directory so we need to manually tell it the location of the proto files.<\/p>\n\n\n\n<p>To do this, press <code>CTRL + ,<\/code> to open the settings page and search for <strong>Proto3<\/strong>.  Then, click on the <strong>Edit in settings.json<\/strong> link and add this option to the Protoc configuration.<\/p>\n\n\n\n<pre class=\"line-numbers language-json\"><code>\n{\n\"protoc\": {\n  \"options\": [\"--proto_path=proto\"]\n  }\n}\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Node.js gRPC Server and Client Overview<\/h2>\n\n\n\n<span id=\"ezoic-pub-video-placeholder-107\"><\/span>\n\n\n\n<p>Here, we will use an API testing tool to make HTTP requests to the Node.js Express server. The request will then be delegated to the right controller which will in tend call the appropriate gRPC client method. <\/p>\n\n\n\n<p>The gRPC client will then go ahead and evoke the right RPC service on the Node.js gRPC API and return the result to the Express controller. Finally, the Express server will return the result in JSON format to the user or client.<\/p>\n\n\n\n<p>To have access to the same Postman collection used in testing the Node.js gRPC API, click on the <a href=\"https:\/\/www.getpostman.com\/collections\/5a90a5c486ed74b331d5\" target=\"_blank\" rel=\"noreferrer noopener\">Postman Collection<\/a> link, and copy the JSON document.<\/p>\n\n\n\n<p>Next, click on the import button in your Postman software, choose the &#8220;<strong>Raw text<\/strong>&#8221; option, and paste the JSON document. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"717\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/import-the-postman-collection-1024x717.png\" alt=\"import the postman collection\" class=\"wp-image-7364\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/import-the-postman-collection-1024x717.png 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/import-the-postman-collection-300x210.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/import-the-postman-collection-768x538.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/import-the-postman-collection-100x70.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/import-the-postman-collection-643x450.png 643w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/import-the-postman-collection.png 1064w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Then, click on the <strong>Continue<\/strong> button to move to the next screen before clicking the <strong>Import<\/strong> button to add the collection.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Calling the Create Record RPC Service<\/h3>\n\n\n\n<p>To add a new record to the database, the client or user will make a <strong>POST<\/strong> request to the <code>localhost:8080\/api\/posts<\/code> endpoint on the Node.js express server. The Express server will then route the request to the right controller where the gRPC client will evoke the <em><code>CreatePost<\/code><\/em> RPC service on the gRPC server to add the new record to the database.<\/p>\n\n\n\n<p>Next, the newly-created record returned to the gRPC client will be sent to the user or the client in JSON format.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"879\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-api-create-a-new-document-1024x879.webp\" alt=\"nodejs grpc api create a new document\" class=\"wp-image-7498\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-api-create-a-new-document-1024x879.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-api-create-a-new-document-300x258.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-api-create-a-new-document-768x659.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-api-create-a-new-document-100x86.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-api-create-a-new-document-524x450.webp 524w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-api-create-a-new-document.webp 1194w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Calling the Update Record RPC Service<\/h3>\n\n\n\n<p>To update an existing record in the database, a <strong>PATCH<\/strong> request will be made to the <code>localhost:8080\/api\/posts\/:postId<\/code> endpoint with the payload in the request body. The gRPC client will then evoke the <em><strong>UpdatePost<\/strong><\/em> RPC method on the gRPC server to edit the record in the database.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"934\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-update-a-document-1024x934.webp\" alt=\"nodejs grpc crud api update a document\" class=\"wp-image-7494\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-update-a-document-1024x934.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-update-a-document-300x274.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-update-a-document-768x700.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-update-a-document-100x91.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-update-a-document-494x450.webp 494w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-update-a-document.webp 1123w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Calling the Get Single Record RPC Service<\/h3>\n\n\n\n<p>Similar to the update operation, a <strong>GET<\/strong> request will be made to the <code>localhost:8080\/api\/posts\/:postId<\/code> endpoint with the ID of the record included in the request URL.<\/p>\n\n\n\n<p>The Express server will then route the request to the right handler where the gRPC client will evoke the <em><strong>GetPost<\/strong><\/em> RPC method to return the record that matches the query.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"930\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-get-a-single-document-1024x930.webp\" alt=\"nodejs grpc crud api get a single document\" class=\"wp-image-7496\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-get-a-single-document-1024x930.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-get-a-single-document-300x272.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-get-a-single-document-768x697.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-get-a-single-document-100x91.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-get-a-single-document-496x450.webp 496w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-get-a-single-document.webp 1129w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Calling the Delete Record RPC Service<\/h3>\n\n\n\n<p>Here, a <strong>DELETE<\/strong>  request will be made to the <code>localhost:8080\/api\/posts\/:postId<\/code> endpoint to remove the record that matches the provided ID from the database.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"930\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-delete-a-document-1024x930.webp\" alt=\"nodejs grpc crud api delete a document\" class=\"wp-image-7497\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-delete-a-document-1024x930.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-delete-a-document-300x273.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-delete-a-document-768x698.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-delete-a-document-100x91.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-delete-a-document-495x450.webp 495w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-delete-a-document.webp 1128w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Calling the Get All Records RPC Service<\/h3>\n\n\n\n<p>All the above operations fall under <strong>Unary RPC<\/strong>. To retrieve a list of records from the database, we&#8217;ll use the <strong>server-side streaming RPC<\/strong> method instead of Unary RPC.<\/p>\n\n\n\n<p>So a <strong>GET<\/strong> request will be made to the <code>localhost:8080\/api\/posts<\/code> endpoint where the <em><strong>GetPosts<\/strong><\/em> RPC service will be evoked to stream the records to the gRPC client.<\/p>\n\n\n\n<p>Next, we will then listen to the &#8220;<strong>data<\/strong>&#8221; event emitted by the gRPC client and push the returned documents into an array before returning it to the client in JSON format.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"932\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-retrieve-all-documents-1024x932.webp\" alt=\"nodejs grpc crud api retrieve all documents\" class=\"wp-image-7495\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-retrieve-all-documents-1024x932.webp 1024w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-retrieve-all-documents-300x273.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-retrieve-all-documents-768x699.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-retrieve-all-documents-100x91.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-retrieve-all-documents-494x450.webp 494w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-retrieve-all-documents.webp 1124w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Setup and Installation<\/h2>\n\n\n\n<p>To begin, create a new folder and open it with your preferred IDE or text editor. You can name it <code>grpc-node-prisma<\/code> .<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir grpc-node-prisma &amp;&amp; cd grpc-node-prisma &amp;&amp; code .<\/code><\/pre>\n\n\n\n<p>Next, initialize the Node.js TypeScript project with the following commands:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn init -y &amp;&amp; yarn add -D typescript &amp;&amp; yarn tsc --init\n# or\nnpx init -y &amp;&amp; npx install -D typescript &amp;&amp; npx tsc --init \n<\/code>\n<\/pre>\n\n\n\n<p>This gRPC API example is based on a PostgreSQL database, however, you can use any database server supported by Prisma. <\/p>\n\n\n\n<p>Now to set up the Postgres database, we will use Docker because it&#8217;s easier to set up and maintain. To do that, create a <strong>docker-compose.yml<\/strong> file in the root directory and add the following code.<\/p>\n\n\n\n<p><strong>docker-compose.yml<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nversion: '3'\nservices:\n  postgres:\n    image: postgres\n    container_name: postgres\n    ports:\n      - '6500:5432'\n    restart: always\n    env_file:\n      - .\/.env\n    volumes:\n      - postgres-db:\/var\/lib\/postgresql\/data\nvolumes:\n  postgres-db:\n<\/code>\n<\/pre>\n\n\n\n<p>The above configuration will pull the <a href=\"https:\/\/hub.docker.com\/_\/postgres\" target=\"_blank\" rel=\"noreferrer noopener\">latest Postgres image<\/a> from Docker Hub, build the image, run the Postgres server in a Docker container, and map <strong>port 6500<\/strong> to the default Postgres port.<\/p>\n\n\n\n<p>Since we used placeholders for the Postgres credentials in the <strong>docker-compose.yml<\/strong> file, create a <code>.env<\/code> file in the root directory and add the following environment variables.<\/p>\n\n\n\n<p><strong>.env<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nDATABASE_PORT=6500\nPOSTGRES_PASSWORD=password123\nPOSTGRES_USER=postgres\nPOSTGRES_DB=grpc-node-prisma\nPOSTGRES_HOST=postgres\nPOSTGRES_HOSTNAME=127.0.0.1\n<\/code>\n<\/pre>\n\n\n\n<p>Now run this command to start the Postgres Docker container:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\ndocker-compose up -d\n<\/code>\n<\/pre>\n\n\n\n<p>Stop the Docker container with this command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\ndocker-compose down\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Create the Protobuf Messages<\/h2>\n\n\n\n<p>Before we can generate the TypeScript files with the Protobuf compiler, we first need to create the Protobuf messages and RPC services using the <a href=\"https:\/\/developers.google.com\/protocol-buffers\/docs\/proto3\" target=\"_blank\" rel=\"noreferrer noopener\">protocol buffer language<\/a>.<\/p>\n\n\n\n<p>One recommendation is to put all the Protobuf files in one folder. This will enable us to generate the gRPC server and client interfaces easily.<\/p>\n\n\n\n<p>To do that, create a <strong>proto<\/strong> folder in the root directory to hold all the Protocol buffer files.<\/p>\n\n\n\n<p>Next, create a <code>proto\/post.proto<\/code> file and add the following Protobuf messages:<\/p>\n\n\n\n<p><strong>proto\/post.proto<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nsyntax = \"proto3\";\n\nimport \"google\/protobuf\/timestamp.proto\";\n\nmessage Post {\n  string id = 1;\n  string title = 2;\n  string content = 3;\n  string category = 4;\n  string image = 5;\n  bool published = 6;\n  google.protobuf.Timestamp created_at = 7;\n  google.protobuf.Timestamp updated_at = 8;\n}\n\nmessage PostResponse { Post post = 1; }\n<\/code>\n<\/pre>\n\n\n\n<p>Quite a lot happening in the above, let&#8217;s break it down. First, we specified the version of the Protobuf language on the first line of the file &#8211; in this case, we used <strong>proto3<\/strong>.<\/p>\n\n\n\n<p>Since the <a href=\"https:\/\/github.com\/protocolbuffers\/protobuf\/blob\/main\/src\/google\/protobuf\/timestamp.proto\" target=\"_blank\" rel=\"noreferrer noopener\">Timestamp<\/a> type is not included in the built-in Protobuf types, we imported it from the Google standard library. This library contains popular data types not included in the Porotobuf built-in types.<\/p>\n\n\n\n<p>To define a Protobuf field, we specified the data type followed by the field name and a unique identifier. The gRPC framework will use the unique identifier under the hood to identify the fields in the&nbsp;<a href=\"https:\/\/developers.google.com\/protocol-buffers\/docs\/encoding\" target=\"_blank\" rel=\"noreferrer noopener\">message binary format<\/a>.<\/p>\n\n\n\n<p>The unique identifier or number can range from <strong>1<\/strong> to <strong>2047<\/strong>. The number between <strong>1<\/strong> and <strong>15<\/strong> will take one byte to encode, whereas the numbers ranging from <strong>16<\/strong> to <strong>2047<\/strong> will take two bytes to encode.<\/p>\n\n\n\n<p>So it&#8217;s recommended to use field numbers between <strong>1<\/strong> to <strong>15<\/strong> if possible. This will reduce the size of the Protobuf message.<\/p>\n\n\n\n<p>Visit the <a href=\"https:\/\/developers.google.com\/protocol-buffers\/docs\/encoding#structure\" target=\"_blank\" rel=\"noreferrer noopener\">Protocol Buffer Encoding<\/a> website to learn more.<\/p>\n\n\n\n<p>With that out of the way, let&#8217;s create the Protobuf message to contain the fields required to add a new record to the database.<\/p>\n\n\n\n<p><strong>proto\/rpc_create_post.proto<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nsyntax = \"proto3\";\n\npackage post;\n\nmessage CreatePostRequest {\n  string title = 1;\n  string content = 2;\n  string category = 3;\n  string image = 4;\n  bool published = 5;\n}\n<\/code>\n<\/pre>\n\n\n\n<p>In the above, we used the <strong>package<\/strong> keyword provided by <strong>proto3<\/strong> to define a package name for the Protobuf message.<\/p>\n\n\n\n<p><strong>proto\/rpc_update_post.proto<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nsyntax = \"proto3\";\n\npackage post;\n\nmessage UpdatePostRequest {\n  string id = 1;\n  optional string title = 2;\n  optional string content = 3;\n  optional string category = 4;\n  optional string image = 5;\n  optional bool published = 6;\n}\n<\/code>\n<\/pre>\n\n\n\n<p>The above Protobuf message is similar to the <em><strong>CreatePostRequest<\/strong><\/em> message except that we used the <strong>optional<\/strong> keyword on the optional fields.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Create the gRPC Services<\/h2>\n\n\n\n<p>Now that we have the Protobuf messages defined, let&#8217;s create a <code>proto\/services.proto<\/code> file to contain the RPC definitions.<\/p>\n\n\n\n<p>Since we have some of the Protobuf messages defined in other files, we will use the <strong>import<\/strong> keyword to include those files.<\/p>\n\n\n\n<p>Here, we will create four Unary gRPC services and a Server streaming RPC service. The Server streaming gRPC method will allow us to stream the list of records retrieved from the database to the gRPC client.<\/p>\n\n\n\n<p>Alternatively, we can use the <strong>repeated<\/strong> keyword on the field specified in the Protobuf response message to indicate that it&#8217;s an array of records. This approach will work but it&#8217;s considered bad practice. <\/p>\n\n\n\n<p><strong>proto\/services.proto<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nsyntax = \"proto3\";\n\nimport \"post.proto\";\nimport \"rpc_create_post.proto\";\nimport \"rpc_update_post.proto\";\n\n\/\/ Post Service\nservice PostService {\n  rpc CreatePost(CreatePostRequest) returns (PostResponse) {}\n  rpc GetPost(PostRequest) returns (PostResponse) {}\n  rpc GetPosts(GetPostsRequest) returns (stream Post) {}\n  rpc UpdatePost(UpdatePostRequest) returns (PostResponse) {}\n  rpc DeletePost(PostRequest) returns (DeletePostResponse) {}\n}\n\nmessage GetPostsRequest {\n  int64 page = 1;\n  int64 limit = 2;\n}\n\nmessage PostRequest { string id = 1; }\n\nmessage DeletePostResponse { bool success = 1; }\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>CreatePost<\/code> &#8211; This Unary RPC method will be evoked to add a new record to the database.<\/li>\n\n\n\n<li><code>GetPost<\/code> &#8211; This Unary RPC service will be evoked to return a single record from the database.<\/li>\n\n\n\n<li><code>GetPosts<\/code> &#8211; This Server streaming RPC service will be evoked to return a stream of the found records.<\/li>\n\n\n\n<li><code>UpdatePost<\/code> &#8211; This Unary RPC service will be evoked to update a record in the database.<\/li>\n\n\n\n<li><code>DeletePost<\/code> &#8211; This Unary RPC method will be evoked to remove a record from the database.<\/li>\n<\/ul>\n\n\n\n<p>Before we can use gRPC in Node.js, we need to install these two dependencies to help us load the Protobuf files and set up the gRPC server and client.<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nyarn add @grpc\/grpc-js @grpc\/proto-loader\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>@grpc\/grpc-js<\/code> &#8211; This library contains the <strong>gRPC<\/strong> implementation for Nodejs.<\/li>\n\n\n\n<li><code>@grpc\/proto-loader<\/code> &#8211; This library contains helper functions for loading <strong>.proto<\/strong> files.<\/li>\n<\/ul>\n\n\n\n<p>Next, let&#8217;s create a bash script to help us generate the TypeScript files from the defined Protobuf files.<\/p>\n\n\n\n<p><strong>proto-gen.sh<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-bash\"><code>\n#!\/bin\/bash\n\nrm -rf pb\/\nyarn proto-loader-gen-types --longs=String --enums=String --defaults --keepCase --oneofs --grpcLib=@grpc\/grpc-js --outDir=pb\/ proto\/*.proto\n<\/code>\n<\/pre>\n\n\n\n<p>If by any chance the TypeScript files are not being generated then run the individual commands directly in the terminal.<\/p>\n\n\n\n<p>When you run the above script, the <code>@grpc\/proto-loader<\/code> package will generate the TypeScript files and output the results into a <code>pb\/<\/code> folder.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"837\" height=\"638\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/generated-gRPC-typescript-files-in-nodejs.png\" alt=\"generated gRPC typescript files in nodejs\" class=\"wp-image-7561\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/generated-gRPC-typescript-files-in-nodejs.png 837w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/generated-gRPC-typescript-files-in-nodejs-300x229.png 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/generated-gRPC-typescript-files-in-nodejs-768x585.png 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/generated-gRPC-typescript-files-in-nodejs-100x76.png 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/generated-gRPC-typescript-files-in-nodejs-590x450.png 590w\" sizes=\"auto, (max-width: 837px) 100vw, 837px\" \/><\/figure>\n\n\n\n<p>Now run the bash script to generate the TypeScript files with this command:<\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\n.\/proto-gen.sh\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Model the API Data with Prisma<\/h2>\n\n\n\n<p>Prisma is basically an ORM for TypeScript and Node.js. The code in this project can be adapted to work with any database server supported by Prisma.<\/p>\n\n\n\n<p>First things first, let&#8217;s install the Prisma CLI and the Client:<\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nyarn add -D prisma &amp;&amp; yarn add @prisma\/client\n# or\nnpm install -D prisma &amp;&amp; npm install @prisma\/client\n<\/code>\n<\/pre>\n\n\n\n<p>Now let&#8217;s run the Prisma <strong>init<\/strong> command with the <code>--datasource-provider<\/code> flag to generate the schema file. <\/p>\n\n\n\n<pre class=\"line-numbers language-shell\"><code>\nmkdir server &amp;&amp; cd server &amp;&amp; yarn prisma init --datasource-provider postgresql\n# or\nmkdir server &amp;&amp; cd server &amp;&amp; npx prisma init --datasource-provider postgresql\n<\/code>\n<\/pre>\n\n\n\n<p>The above command will generate <code>prisma\/schema.prisma<\/code> , <code>.gitignore<\/code> , and <code>.env<\/code> files in the server folder.<\/p>\n\n\n\n<p>Now delete the <code>.gitignore<\/code> , and <code>.env<\/code> files from the server folder.<\/p>\n\n\n\n<p>Next, add the PostgreSQL connection URL to the <code>.env<\/code> file in the root directory. The <code>DATABASE_URL<\/code> will be used by Prisma behind the scene to create connection pools between the Node.js app and the Postgres server.<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nDATABASE_PORT=6500\nPOSTGRES_PASSWORD=password123\nPOSTGRES_USER=postgres\nPOSTGRES_DB=grpc-node-prisma\nPOSTGRES_HOST=postgres\nPOSTGRES_HOSTNAME=127.0.0.1\n\nDATABASE_URL=\"postgresql:\/\/postgres:password123@localhost:6500\/grpc-node-prisma?schema=public\"\n<\/code>\n<\/pre>\n\n\n\n<p>Now let&#8217;s define the API data structure with Prisma. Open the <code>server\/prisma\/schema.prisma<\/code> file and add the following schema definition.<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n\ndatasource db {\n  provider = \"postgresql\"\n  url      = env(\"DATABASE_URL\")\n}\n\nmodel Post {\n  id         String   @id @default(uuid())\n  title      String   @unique @db.VarChar(255)\n  content    String\n  category   String\n  image      String\n  published  Boolean  @default(false)\n  created_at DateTime @default(now())\n  updated_at DateTime @updatedAt\n\n  @@map(name: \"posts\")\n}\n\n<\/code>\n<\/pre>\n\n\n\n<p>In the above, we defined a <strong>Post<\/strong> model which will be transformed by the Prisma engine into an SQL table.<\/p>\n\n\n\n<p>Add the following scripts to the <strong>package.json<\/strong> file:<\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n{\n\"scripts\": {\n    \"db:migrate\": \"npx prisma migrate dev --name post-entity --create-only --schema .\/server\/prisma\/schema.prisma\",\n    \"db:generate\": \" npx prisma generate --schema .\/server\/prisma\/schema.prisma\",\n    \"db:push\": \"npx prisma db push --schema .\/server\/prisma\/schema.prisma\"\n  },\n}\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>db:migrate<\/code> &#8211; This command will create the database migration file without applying it.<\/li>\n\n\n\n<li><code>db:generate<\/code> &#8211; This command will generate the Prisma client.<\/li>\n\n\n\n<li><code>db:push<\/code> &#8211; This command will push the migration file to the database and keep the Prisma schema in sync with the database schema.<\/li>\n<\/ul>\n\n\n\n<p>With that said, run this command to create the migration file and push the changes to the database.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Make sure the PostgreSQL Docker container is running<\/p>\n<\/blockquote>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nyarn db:migrate &amp;&amp; yarn db:generate &amp;&amp; yarn db:push\n# or\nnpm run db:migrate &amp;&amp; npm run db:generate &amp;&amp; npm run db:push\n<\/code>\n<\/pre>\n\n\n\n<p>Open any Postgres client and sign in with the credentials provided in the <code>.env<\/code> file to see the SQL table added by the Prisma CLI.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"914\" height=\"609\" src=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-data-in-postgres.webp\" alt=\"nodejs grpc crud api data in postgres\" class=\"wp-image-7576\" srcset=\"https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-data-in-postgres.webp 914w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-data-in-postgres-300x200.webp 300w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-data-in-postgres-768x512.webp 768w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-data-in-postgres-100x67.webp 100w, https:\/\/codevoweb.com\/wp-content\/uploads\/2022\/09\/nodejs-grpc-crud-api-data-in-postgres-675x450.webp 675w\" sizes=\"auto, (max-width: 914px) 100vw, 914px\" \/><\/figure>\n\n\n\n<p>Next, let&#8217;s create a utility function to create a connection pool between the Node.js server and the PostgreSQL server. This function will also export the Prisma Client to be used in the RPC controllers.<\/p>\n\n\n\n<p>Create a <code>server\/utils\/prisma.ts<\/code> file and add the following code:<\/p>\n\n\n\n<p><strong>server\/utils\/prisma.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { PrismaClient } from '@prisma\/client';\n\ndeclare global {\n  var prisma: PrismaClient | undefined;\n}\n\nexport const prisma = global.prisma || new PrismaClient();\n\nif (process.env.NODE_ENV !== 'production') {\n  global.prisma = prisma;\n}\n\nasync function connectDB() {\n  try {\n    await prisma.$connect();\n    console.log('? Database connected successfully');\n  } catch (error) {\n    console.log(error);\n    process.exit(1);\n  } finally {\n    await prisma.$disconnect();\n  }\n}\n\nexport default connectDB;\n<\/code>\n<\/pre>\n\n\n\n<p>In every API architecture, it&#8217;s always recommended to create services to access and mutate the database instead of doing that directly in the controllers.<\/p>\n\n\n\n<p>To do this, create a <code>server\/services\/post.service.ts<\/code> file and add the following service definitions:<\/p>\n\n\n\n<p><strong>server\/services\/post.service.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { Prisma, Post } from '@prisma\/client';\nimport { prisma } from '..\/utils\/prisma';\n\nexport const createPost = async (input: Prisma.PostCreateInput) =&gt; {\n  return (await prisma.post.create({\n    data: input,\n  })) as Post;\n};\n\nexport const findPost = async (\n  where: Partial&lt;Prisma.PostWhereInput&gt;,\n  select?: Prisma.PostSelect\n) =&gt; {\n  return (await prisma.post.findFirst({\n    where,\n    select,\n  })) as Post;\n};\n\nexport const findUniquePost = async (\n  where: Prisma.PostWhereUniqueInput,\n  select?: Prisma.PostSelect\n) =&gt; {\n  return (await prisma.post.findUnique({\n    where,\n    select,\n  })) as Post;\n};\n\nexport const findAllPosts = async (\n  {page, limit, select}:\n { page: number,\n  limit: number,\n  select?: Prisma.PostSelect},\n) =&gt; {\n  const take = limit || 10;\n  const skip = (page - 1 ) * limit\n  return (await prisma.post.findMany({\n    select,\n    skip,\n    take,\n  })) as Post[];\n};\n\n\nexport const updatePost = async (\n  where: Partial&lt;Prisma.PostWhereUniqueInput&gt;,\n  data: Prisma.PostUpdateInput,\n  select?: Prisma.PostSelect\n) =&gt; {\n  return (await prisma.post.update({ where, data, select })) as Post;\n};\n\nexport const deletePost = async (where: Prisma.PostWhereUniqueInput)=&gt; {\n  return await prisma.post.delete({where})\n}\n\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Create the RPC Handlers<\/h2>\n\n\n\n<p>In this section, we will create five RPC handlers that will be evoked to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><em><code>createPostHandler<\/code><\/em> &#8211; This RPC handler will be evoked to insert a new record.<\/li>\n\n\n\n<li><em><code>UpdatePostHandler<\/code><\/em> &#8211; This RPC handler will be evoked to update an existing record.<\/li>\n\n\n\n<li><em><code>findPostHandler<\/code><\/em> &#8211; This RPC handler will be evoked to retrieve a single record.<\/li>\n\n\n\n<li><em><code>findAllPostsHandler<\/code><\/em> &#8211; This RPC handler will be evoked to retrieve a paginated list of records.<\/li>\n\n\n\n<li><em><code>deletePostHandler<\/code><\/em> &#8211; This RPC handler will be evoked to delete a record.<\/li>\n<\/ul>\n\n\n\n<p>But before we start creating the RPC handlers, create a <code>server\/controllers\/post.controller.ts<\/code> file and add the following imports.<\/p>\n\n\n\n<p><strong>server\/controllers\/post.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\nimport * as grpc from '@grpc\/grpc-js';\nimport { DeletePostResponse } from '..\/..\/pb\/DeletePostResponse';\nimport { GetPostsRequest__Output } from '..\/..\/pb\/GetPostsRequest';\nimport { Post } from '..\/..\/pb\/Post';\nimport { CreatePostRequest__Output } from '..\/..\/pb\/post\/CreatePostRequest';\nimport { UpdatePostRequest__Output } from '..\/..\/pb\/post\/UpdatePostRequest';\nimport { PostRequest__Output } from '..\/..\/pb\/PostRequest';\nimport { PostResponse } from '..\/..\/pb\/PostResponse';\nimport { createPost, deletePost, findAllPosts, findPost, findUniquePost, updatePost } from '..\/services\/post.service';\n\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Create a New Record RPC Handler<\/h3>\n\n\n\n<p>This RPC handler will call the <code>createPost<\/code> service we defined above to add the new entity to the database. Since we added a unique constraint on the title field, a duplicate error will be returned by Prisma if a record with that title already exists.<\/p>\n\n\n\n<p><strong>server\/controllers\/post.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\n\/\/ [...] imports\n\n\/\/ [...] Create a New Record RPC Handler\nexport const createPostHandler = async (\n  req: grpc.ServerUnaryCall&lt;CreatePostRequest__Output, PostResponse&gt;,\n  res: grpc.sendUnaryData&lt;PostResponse&gt;\n) =&gt; {\n  try {\n   const post = await createPost({\n    title: req.request.title,\n    content: req.request.content,\n    image: req.request.image,\n    category: req.request.category,\n    published: true,\n   })\n\n   res(null, {\n    post: {\n      id: post.id,\n      title: post.title,\n      content: post.content,\n      image: post.image,\n      published: post.published,\n      created_at: {\n          seconds: post.created_at.getTime() \/ 1000,\n        },\n        updated_at: {\n          seconds: post.updated_at.getTime() \/ 1000,\n        },\n    }\n   })\n  } catch (err: any) {\n    if (err.code === 'P2002') {\n      res({\n        code: grpc.status.ALREADY_EXISTS,\n        message: 'Post with that title already exists',\n      });\n    }\n    res({\n      code: grpc.status.INTERNAL,\n      message: err.message,\n    });\n  }\n};\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Update Record RPC Handler<\/h3>\n\n\n\n<p>Here, we will extract the ID parameter from the request and query the database to check if a record with that ID exists.<\/p>\n\n\n\n<p>Next, we will call the <code>updatePost<\/code> service to update the record if it exists in the database.<\/p>\n\n\n\n<p><strong>server\/controllers\/post.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\n\/\/ [...] imports\n\n\/\/ [...] Create a New Record RPC Handler\n\n\/\/ [...] Update Record RPC Handler\nexport const UpdatePostHandler = async (req: grpc.ServerUnaryCall&lt;UpdatePostRequest__Output, PostResponse&gt;, res: grpc.sendUnaryData&lt;PostResponse&gt;)=&gt; {\ntry {\n const postExists =  await findPost({id: req.request.id})\n\n  if (!postExists) {\n      res({\n        code: grpc.status.NOT_FOUND,\n        message: 'No post with that ID exists',\n      });\n      return\n    }\n  const updatedPost = await updatePost({id: req.request.id},{\n    title: req.request.title,\n    content: req.request.content,\n    category: req.request.category,\n    image: req.request.image,\n    published: req.request.published,\n  })\n\n  res(null, {\n    post: {\n      id: updatedPost.id,\n      title: updatedPost.title,\n      content: updatedPost.content,\n      image: updatedPost.image,\n      published: updatedPost.published,\n      created_at: {\n          seconds: updatedPost.created_at.getTime() \/ 1000,\n        },\n        updated_at: {\n          seconds: updatedPost.updated_at.getTime() \/ 1000,\n        },\n    }\n  })\n} catch (err: any) {\n  res({\n      code: grpc.status.INTERNAL,\n      message: err.message,\n    });\n}\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Retrieve a Single Record RPC Handler<\/h3>\n\n\n\n<p>Similar to the previous handler, we will retrieve the ID of the record from the request and query the database to check if a record with that ID exists in the database.<\/p>\n\n\n\n<p><strong>server\/controllers\/post.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n\/\/ [...] imports\n\n\/\/ [...] Create a New Record RPC Handler\n\n\/\/ [...] Update Record RPC Handler\n\n\/\/ [...] Retrieve a Single Record RPC Handler\nexport const findPostHandler = async (req: grpc.ServerUnaryCall&lt;PostRequest__Output, PostResponse&gt;, res: grpc.sendUnaryData&lt;PostResponse&gt;)=&gt; {\ntry {\n  const post = await findUniquePost({id: req.request.id})\n\n  if (!post) {\n      res({\n        code: grpc.status.NOT_FOUND,\n        message: 'No post with that ID exists',\n      });\n      return\n    }\n\n  res(null, {\n    post: {\n      id: post.id,\n      title: post.title,\n      content: post.content,\n      image: post.image,\n      published: post.published,\n      created_at: {\n          seconds: post.created_at.getTime() \/ 1000,\n        },\n        updated_at: {\n          seconds: post.updated_at.getTime() \/ 1000,\n        },\n    }\n  })\n} catch (err: any) {\n  res({\n      code: grpc.status.INTERNAL,\n      message: err.message,\n    });\n}\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Delete a Record RPC Handler<\/h3>\n\n\n\n<p>This handler is the simplest of all. In this RPC handler, we will extract the ID from the request and query the database to remove the record with that ID from the database.<\/p>\n\n\n\n<p><strong>server\/controllers\/post.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-js\"><code>\n\/\/ [...] imports\n\n\/\/ [...] Create a New Record RPC Handler\n\n\/\/ [...] Update Record RPC Handler\n\n\/\/ [...] Retrieve a Single Record RPC Handler\n\n\/\/ [...] Delete a Record RPC Handler\nexport const deletePostHandler = async (req: grpc.ServerUnaryCall&lt;PostRequest__Output, DeletePostResponse&gt;, res: grpc.sendUnaryData&lt;DeletePostResponse&gt;)=&gt; {\ntry {\n  const postExists =  await findPost({id: req.request.id})\n\n  if (!postExists) {\n      res({\n        code: grpc.status.NOT_FOUND,\n        message: 'No post with that ID exists',\n      });\n      return\n    }\n    \n  const post = await deletePost({id: req.request.id})\n\n  if (!post) {\n      res({\n        code: grpc.status.NOT_FOUND,\n        message: 'No post with that ID exists',\n      });\n      return\n    }\n\n  res(null, {\n    success: true\n  })\n} catch (err: any) {\n  res({\n      code: grpc.status.INTERNAL,\n      message: err.message,\n    });\n}\n}\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Retrieve all Records RPC Handler<\/h3>\n\n\n\n<p>This RPC handler is responsible for streaming the list of records to the gRPC client. First, we will call the <code>findAllPosts<\/code> service to return a paginated list of the records.<\/p>\n\n\n\n<p>Next, we will loop through the results and evoke the <code>call.write()<\/code> method to write the data to a stream.<\/p>\n\n\n\n<p>After all the found records have been written to the stream, we will evoke the <code><em>call.end<\/em>()<\/code> method to close the stream.<\/p>\n\n\n\n<p><strong>server\/controllers\/post.controller.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\n\/\/ [...] imports\n\n\/\/ [...] Create a New Record RPC Handler\n\n\/\/ [...] Update Record RPC Handler\n\n\/\/ [...] Retrieve a Single Record RPC Handler\n\n\/\/ [...] Delete a Record RPC Handler\n\n\/\/ [...] Retrieve all Records RPC Handler\nexport const findAllPostsHandler = async (call: grpc.ServerWritableStream&lt;GetPostsRequest__Output, Post&gt;)=&gt;{\n  try {\n    const {page, limit} = call.request\n    const posts = await findAllPosts({page: parseInt(page), limit: parseInt(limit)})\n\n    for(let i= 0; i &lt; posts.length; i++){\n      const post = posts[i]\n      call.write({\n        id: post.id,\n        title: post.title,\n        content: post.content,\n        category: post.category,\n        image: post.image,\n        published: post.published,\n        created_at: {\n          seconds: post.created_at.getTime() \/ 1000,\n        },\n        updated_at: {\n          seconds: post.updated_at.getTime() \/ 1000,\n        },\n\n      })\n    }\n    call.end()\n    \n  } catch (error: any) {\n    console.log(error)\n  }\n}\n<\/code>\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Create the Node.js gRPC Server<\/h2>\n\n\n\n<p>Now that we have all the RPC handlers defined, we are now ready to create the gRPC server to listen to requests.<\/p>\n\n\n\n<p>But before that, create a <code>server\/config\/default.ts<\/code> file and add the following code to load the environment variables from the <code>.env<\/code> file.<\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nyarn add dotenv\n<\/code>\n<\/pre>\n\n\n\n<p><strong>server\/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  dbUri: string;\n} = {\n  port: 8000,\n  dbUri: process.env.DATABASE_URL as string,\n};\n\nexport default customConfig;\n<\/code>\n<\/pre>\n\n\n\n<p>Next, create a <code>server\/app.ts<\/code> file and add the following code to load the Protobuf file and set up the Node.js gRPC server.<\/p>\n\n\n\n<p><strong>server\/app.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport path from 'path';\nimport * as grpc from '@grpc\/grpc-js';\nimport * as protoLoader from '@grpc\/proto-loader';\nimport { ProtoGrpcType } from '..\/pb\/services';\nimport {PostServiceHandlers} from \"..\/pb\/PostService\"\nimport customConfig from '.\/config\/default';\nimport connectDB from '.\/utils\/prisma';\nimport { createPostHandler, deletePostHandler, findAllPostsHandler, findPostHandler, UpdatePostHandler } from '.\/controllers\/post.controller';\n\nconst options: protoLoader.Options = {\n  keepCase: true,\n  longs: String,\n  enums: String,\n  defaults: true,\n  oneofs: true,\n};\n\nconst PORT = customConfig.port;\nconst PROTO_FILE = '..\/proto\/services.proto';\nconst packageDef = protoLoader.loadSync(\n  path.resolve(__dirname, PROTO_FILE),\n  options\n);\n\nconst proto = grpc.loadPackageDefinition(\n  packageDef\n) as unknown as ProtoGrpcType;\n\nconst server = new grpc.Server();\n\n\/\/ Post Services\nserver.addService(proto.PostService.service, {\n  CreatePost: (req, res)=&gt; createPostHandler(req,res),\n  UpdatePost: (req,res)=&gt; UpdatePostHandler(req,res),\n  DeletePost: (req,res)=&gt; deletePostHandler(req,res),\n  GetPost: (req, res)=&gt; findPostHandler(req,res),\n  GetPosts: (call)=&gt; findAllPostsHandler(call)\n  \n} as PostServiceHandlers)\nserver.bindAsync(\n  `0.0.0.0:${PORT}`,\n  grpc.ServerCredentials.createInsecure(),\n  (err, port) =&gt; {\n    if (err) {\n      console.error(err);\n      return;\n    }\n    server.start();\n    connectDB();\n    console.log(`? Server listening on ${port}`);\n  }\n);\n<\/code>\n<\/pre>\n\n\n\n<p>Quite a lot going on in the above, let&#8217;s break it down. First, we used the <code>@grpc\/proto-loader<\/code> package to load the Protobuf file.<\/p>\n\n\n\n<p>Next, we provided the returned package definition object to the <code>loadPackageDefinition<\/code> function provided by the <code>@grpc\/grpc-js<\/code> library.<\/p>\n\n\n\n<p>Next, we called the <code><em>grpc.Server<\/em>()<\/code> method to initialize a new gRPC server after the gRPC object has been returned.<\/p>\n\n\n\n<p>On the server, we called the <code><em>addService<\/em>()<\/code> method to list all the RPC services and call their appropriate handlers.<\/p>\n\n\n\n<p>Finally, we called the <code><em>bindAsync<\/em>()<\/code> method to bind the server connection to a specified IP and port. Since gRPC works over <strong>HTTP\/2<\/strong>, we had to create an insecure connection for it to work on our local host.<\/p>\n\n\n\n<p>We are done with the Node.js gRPC server. Install this dependency to help us start and hot-reload the gRPC server.<\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nyarn add -D ts-node-dev\n# or\nnpm install -D ts-node-dev\n<\/code>\n<\/pre>\n\n\n\n<p>Next, add the following scripts to the <strong>package.json<\/strong> file:<\/p>\n\n\n\n<p><strong>package.json<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\n{\n\"scripts\": {\n    \"start:server\": \"ts-node-dev --respawn --transpile-only server\/app.ts\",\n    \"start:client\": \"ts-node-dev --respawn --transpile-only client\/app.ts\",\n    \"db:migrate\": \"npx prisma migrate dev --name post-entity --create-only --schema .\/server\/prisma\/schema.prisma\",\n    \"db:generate\": \" npx prisma generate --schema .\/server\/prisma\/schema.prisma\",\n    \"db:push\": \"npx prisma db push --schema .\/server\/prisma\/schema.prisma\"\n  }\n}\n<\/code>\n<\/pre>\n\n\n\n<p>Open the integrated terminal in your IDE and run <code>yarn start:server<\/code> to start the gRPC server on port <strong>8000<\/strong>.<\/p>\n\n\n\n<p>Now that we have the server up and running, we need to create the gRPC client that understands the Protobuf interface the server serves.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Create the gRPC Client with Node.js<\/h2>\n\n\n\n<p>Now let&#8217;s build the application client, start by installing the following dependencies:<\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nyarn add zod express &amp;&amp; yarn add -D morgan @types\/express @types\/morgan\n<\/code>\n<\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>zod<\/code> &#8211; TypeScript-first schema validation library.<\/li>\n\n\n\n<li><code>express<\/code> &#8211; a Node.js web framework.<\/li>\n\n\n\n<li><code>morgan<\/code> &#8211; This library will log the HTTP requests to the console.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Create the Validation Schema with Zod<\/h3>\n\n\n\n<p>Next, create the following Zod schemas that will be provided to the Express web framework to validate the incoming request body. This will ensure that the required fields are provided in the request payload.<\/p>\n\n\n\n<p><strong>client\/schema\/post.schema.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { z } from 'zod';\n\nexport const createPostSchema = z.object({\n  body: z.object({\n    title: z.string({\n      required_error: 'Title is required',\n    }),\n    content: z.string({\n      required_error: 'Content is required',\n    }),\n    category: z.string({\n      required_error: 'Category is required',\n    }),\n    published: z.boolean({\n      required_error: 'Published is required',\n    }),\n    image: z.string({\n      required_error: 'Image is required',\n    }),\n  }),\n});\n\nconst params = {\n  params: z.object({\n    postId: z.string(),\n  }),\n};\n\nexport const getPostSchema = z.object({\n  ...params,\n});\n\nexport const updatePostSchema = z.object({\n  ...params,\n  body: z\n    .object({\n      title: z.string(),\n      content: z.string(),\n      category: z.string(),\n      published: z.boolean(),\n      image: z.string(),\n    })\n    .partial(),\n});\n\nexport const deletePostSchema = z.object({\n  ...params,\n});\n\nexport type CreatePostInput = z.TypeOf&lt;typeof createPostSchema&gt;['body'];\nexport type GetPostInput = z.TypeOf&lt;typeof getPostSchema&gt;['params'];\nexport type UpdatePostInput = z.TypeOf&lt;typeof updatePostSchema&gt;;\nexport type DeletePostInput = z.TypeOf&lt;typeof deletePostSchema&gt;['params'];\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Create a Middleware to Validate the Requests<\/h3>\n\n\n\n<p>Now create a <code>client\/middleware\/validate.ts<\/code> file and add the following Zod middleware that will accept the defined schema as an argument and validate the request before returning the appropriate validation error to the client.<\/p>\n\n\n\n<p><strong>client\/middleware\/validate.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport { z } from 'zod';\nimport {Request, Response, NextFunction} from \"express\"\n\nconst validate =\n  (schema: z.AnyZodObject) =&gt;\n  async (req: Request,res: Response, next: NextFunction): Promise&lt;any&gt; =&gt; {\n    try {\n      schema.parse({\n        params:req.params,\n        query: req.query,\n        body: req.body,\n      });\n\n      next();\n    } catch (err: any) {\n      if (err instanceof z.ZodError) {\n        return res.status(400).json({\n          status: \"fail\",\n          message: err.errors\n        })\n    \n      }\n      return res.status(500).json({\n          status: \"error\",\n          message: err.message\n        })\n    }\n  };\nexport default validate;\n<\/code>\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Set up and Export the gRPC Client<\/h3>\n\n\n\n<p>We are now ready to create the gRPC client that will handle the communication with the Node.js gRPC API.<\/p>\n\n\n\n<p>Create a <code>client\/client.ts<\/code> file and add the code snippets below:<\/p>\n\n\n\n<p><strong>client\/client.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport path from 'path';\nimport * as grpc from '@grpc\/grpc-js';\nimport * as protoLoader from '@grpc\/proto-loader';\nimport { ProtoGrpcType } from '..\/pb\/services';\n\nconst options: protoLoader.Options = {\n  keepCase: true,\n  longs: String,\n  enums: String,\n  defaults: true,\n  oneofs: true,\n};\n\nconst PROTO_FILE = '..\/proto\/services.proto';\nconst packageDef = protoLoader.loadSync(\n  path.resolve(__dirname, PROTO_FILE),\n  options\n);\n\nexport const proto = grpc.loadPackageDefinition(\n  packageDef\n) as unknown as ProtoGrpcType;\n\n<\/code>\n<\/pre>\n\n\n\n<p>The above code snippets are similar to the server file <code>server\/app.ts<\/code> because the same gRPC objects handle the client and server instances.<\/p>\n\n\n\n<p>All we did is export the gRPC proto object to be used in other files.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Set up the Express Server<\/h3>\n\n\n\n<p>Now that we have the gRPC proto object, let&#8217;s create a new service over the same IP and port the server is running on.<\/p>\n\n\n\n<p>To use this service contract, we need to create the Express server and add all the CRUD route handlers.<\/p>\n\n\n\n<p><strong>client\/app.ts<\/strong><\/p>\n\n\n\n<pre class=\"line-numbers language-ts\"><code>\nimport * as grpc from '@grpc\/grpc-js';\nimport customConfig from '..\/server\/config\/default';\nimport { proto } from \".\/client\";\nimport express, { Request, Response } from \"express\"\nimport morgan from 'morgan';\nimport validate from '.\/middleware\/validate';\nimport { CreatePostInput, createPostSchema, DeletePostInput, GetPostInput, UpdatePostInput, updatePostSchema } from '.\/schema\/post.schema';\nimport { Post } from '@prisma\/client';\n\nconst client = new proto.PostService(\n  `0.0.0.0:${customConfig.port}`,\n  grpc.credentials.createInsecure()\n);\nconst deadline = new Date();\ndeadline.setSeconds(deadline.getSeconds() + 1);\nclient.waitForReady(deadline, (err) =&gt; {\n  if (err) {\n    console.error(err);\n    return;\n  }\n  onClientReady();\n});\n\nfunction onClientReady() {\n  console.log(\"? gRPC Client is ready\")\n}\n\nconst app = express()\napp.use(express.json())\napp.use(morgan('dev'))\napp.post(\"\/api\/posts\", validate(createPostSchema), async (req: Request&lt;{},{},CreatePostInput&gt;, res: Response)=&gt;{\nconst {title,image,category,content,published} = req.body\n  client.CreatePost(\n    {\n      title,\n      content,\n      category,\n      image,\n      published\n    },\n    (err, data) =&gt; {\n      if (err) {\n        return res.status(400).json({\n          status: \"fail\",\n          message: err.message\n        })\n      }\n      return res.status(201).json({\n          status: \"success\",\n          post: data?.post\n        })\n    }\n  );\n})\n\napp.patch(\"\/api\/posts\/:postId\", validate(updatePostSchema), async (req: Request&lt;UpdatePostInput['params'],{},UpdatePostInput['body']&gt;, res: Response)=&gt;{\nconst {title,image,category,content,published} = req.body\n  client.UpdatePost(\n    {\n      id: req.params.postId,\n      title,\n      content,\n      category,\n      image,\n      published\n    },\n    (err, data) =&gt; {\n      if (err) {\n        return res.status(400).json({\n          status: \"fail\",\n          message: err.message\n        })\n      }\n      return res.status(200).json({\n          status: \"success\",\n          post: data?.post\n        })\n    }\n  );\n})\n\napp.get(\"\/api\/posts\/:postId\", async (req: Request&lt;GetPostInput&gt;, res: Response)=&gt;{\n  client.GetPost(\n    {\n      id: req.params.postId,\n    },\n    (err, data) =&gt; {\n      if (err) {\n        return res.status(400).json({\n          status: \"fail\",\n          message: err.message\n        })\n      }\n      return res.status(200).json({\n          status: \"success\",\n          post: data?.post\n        })\n    }\n  );\n})\n\napp.delete(\"\/api\/posts\/:postId\", async (req: Request&lt;DeletePostInput&gt;, res: Response)=&gt;{\n  client.DeletePost(\n    {\n      id: req.params.postId,\n    },\n    (err, data) =&gt; {\n      if (err) {\n        return res.status(400).json({\n          status: \"fail\",\n          message: err.message\n        })\n      }\n      return res.status(204).json({\n          status: \"success\",\n          data: null\n        })\n    }\n  );\n})\n\napp.get(\"\/api\/posts\", async (req: Request, res: Response)=&gt;{\n  const limit = parseInt(req.query.limit as string) || 10\n  const page = parseInt(req.query.page as string) || 1\n  const posts: Post[] = []\n\n  const stream = client.GetPosts({page, limit})\n  stream.on(\"data\", (data: Post)=&gt; {\n    posts.push(data)\n  })\n\n  stream.on(\"end\", ()=&gt; {\n    console.log(\"? Communication ended\")\n    res.status(200).json({\n          status: \"success\",\n          results: posts.length,\n          posts\n          \n        })\n  })\n\n  stream.on(\"error\", (err)=&gt; {\n    res.status(500).json({\n          status: \"error\",\n          message: err.message\n        })\n  })\n})\n\nconst port = 8080\napp.listen(port, ()=&gt;{\n  console.log(\"? Express client started successfully on port: \"+ port)\n})\n<\/code>\n<\/pre>\n\n\n\n<p>In each HTTP CRUD controller, we evoked each of the actions provided by the interface contract to make the request to the server.<\/p>\n\n\n\n<p>Since the gRPC server is already running on port <strong>8000<\/strong>, we configured the Express server to run on port <strong>8080<\/strong>.<\/p>\n\n\n\n<p>Now split your terminal into two, run <code>yarn start:server<\/code> to start the gRPC server in one half and <code>yarn start:client<\/code> to start the gRPC client and Express server in the other half.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Make sure the PostgreSQL Docker container is running<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>In this article, you&#8217;ve learned how to build a gRPC server and client with TypeScript in a Node.js environment that uses a Postgres database. But there are a lot of things you can do with gRPC. You can follow the previous article <a href=\"\/build-nodejs-grpc-server-and-client-register-and-login-user\">Build a Node.js gRPC Server and Client: Register and Login User<\/a> to add authentication to make the API safer.<\/p>\n\n\n\n<p>You can find the complete source code on <a href=\"https:\/\/github.com\/wpcodevo\/grpc-node-prisma\/tree\/grpc-node-prisma-crud-api\" target=\"_blank\" rel=\"noreferrer noopener\">this GitHub Repository<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Google Remote Procedure Call, also commonly referred to as gRPC is a modern, open-source, high-performance remote procedure call (RPC) framework that is frequently used in&#8230;<\/p>\n","protected":false},"author":1,"featured_media":7605,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[47],"tags":[70,42],"class_list":["post-7485","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-nodejs","tag-grpc-api","tag-nodejs-api"],"acf":[],"_links":{"self":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/7485","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=7485"}],"version-history":[{"count":1,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/7485\/revisions"}],"predecessor-version":[{"id":11254,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/posts\/7485\/revisions\/11254"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media\/7605"}],"wp:attachment":[{"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/media?parent=7485"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/categories?post=7485"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/codevoweb.com\/wp-json\/wp\/v2\/tags?post=7485"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}