{"id":21675,"date":"2018-02-07T17:45:18","date_gmt":"2018-02-07T17:45:18","guid":{"rendered":"http:\/\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup"},"modified":"2025-03-30T13:57:58","modified_gmt":"2025-03-30T20:57:58","slug":"building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup","title":{"rendered":"Building a Smart AI Image Search Tool Using React &#8211; Part 2: UI Components and Server Setup"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>In our <a href=\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_1_app_structure_and_container_component\">first article<\/a>, we built a part of the front-end of our image search tool with the focus mainly on the parent App.js stateful component.<\/p>\n<p>In this article &#8211; part two of a series &#8211; we will continue developing a AI image Search App, in which users can search for content in an image, not just the description. The app is built with React for UI interaction, Cloudinary for image upload and management and Algolia for search.<\/p>\n<p>We will show how to complete the app. <em>I know you must be stoked, I am too<\/em>. We will be building the stateless components of our app and these components without state will mostly receive props from the parent component as with parent-child communication existing in React. Also the server-side of our app will be built and deployed to Webtask.<\/p>\n<h2>Installation<\/h2>\n<p>All build tools and dependencies have been installed in the first part so we can move on with building.<\/p>\n<h2>The Custom Stateless Components<\/h2>\n<p>The stateless components are the ImageList component and the modal component. These display the list of images fetched from Algolia and the upload modal interface respectively. The action of buttons on the interface have already been configured in the parent App.js component and will be passed down to the child stateless components via props as earlier stated.<\/p>\n<h3>The ImageList Component<\/h3>\n<p>This component holds the images saved on the webtask server. In configuring the component, navigate to the ImageList.js file in the components folder and configure it like this:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> {Image, CloudinaryContext, Transformation} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'cloudinary-react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/ImageList.css'<\/span>;\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> ({ hit }) =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"card\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"card-content\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span> <span class=\"hljs-attr\">publicId<\/span>=<span class=\"hljs-string\">{hit.public_id}<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Transformation<\/span> <span class=\"hljs-attr\">format<\/span>=<span class=\"hljs-string\">\u201dauto\u201d<\/span> <span class=\"hljs-attr\">quality<\/span>=<span class=\"hljs-string\">\u201dauto\u201d<\/span> \/&gt;<\/span>\n         <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Image<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span>&gt;<\/span>{hit.description}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>First, we import all dependencies and its CSS file. Since the component is stateless, props are just passed as parameter to the export function which returns JSX elements. The Cloudinary React component exposes <code>Image<\/code> and <code>Transformation<\/code> which helps with fetching the image from the Cloudinary server and setting transformations respectively. The interesting thing is that the transformation sets format and quality to auto. This renders the best optimized format for a give client (browser) and also optimizes the image intelligently.<\/p>\n<p>The elements are styled with Bulma classes to image cards. It can be seen that the image src is the URL received from the hit prop and also its description. Style the ImageList cards with the ImageList.css file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-class\">.card<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-number\">#f5f5f5<\/span>;\n  <span class=\"hljs-attribute\">display<\/span>: inline-block;\n  <span class=\"hljs-attribute\">margin<\/span>: <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">1em<\/span>;\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">100%<\/span>;\n  <span class=\"hljs-attribute\">cursor<\/span>: pointer;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Modal Component<\/h3>\n<p>The Modal component is the interface handling uploads, description creation and saving. Previously we created the Modal.js file in the components folder and its accompanying CSS file. Here we configure this empty file. In Modal.js, import all required dependencies and modules:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'react'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> classNames <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'classnames'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> <span class=\"hljs-string\">'.\/Modal.css'<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>classnames is a module (which we installed earlier) that is used to dynamically assign classes to an element in React. In the Modal component, create a function with a parameter \u2018isActive\u2019 and assign it to the variable modalClass. In the function, the classNames function is called and passed an object containing keys, <code>modal<\/code> and <code>is-active<\/code>. <code>is-active<\/code> is the CSS class to be manipulated.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">...\nconst modalClass = <span class=\"hljs-function\"><span class=\"hljs-params\">isActive<\/span> =&gt;<\/span>\n  classNames({\n    <span class=\"hljs-attr\">modal<\/span>: <span class=\"hljs-literal\">true<\/span>,\n    <span class=\"hljs-string\">'is-active'<\/span>: isActive\n  });\n...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Next, we render the image upload interface and a close button:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">...\nexport <span class=\"hljs-keyword\">default<\/span> ({\n  isActive,\n  toggleModal,\n  onDrop,\n  saveImage,\n  preview,\n  description,\n  pending,\n  handleDescriptionChange,\n  handleDropZoneClick\n}) =&gt; {\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{modalClass(isActive)}<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"modal-background\"<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{toggleModal}<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"modal-content\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"modal-card\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"modal-card-head\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"modal-card-title\"<\/span>&gt;<\/span>Upload an image!<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n              <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{toggleModal}<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"delete\"<\/span>\n              <span class=\"hljs-attr\">aria-label<\/span>=<span class=\"hljs-string\">\"close\"<\/span>\n            \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n        <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{toggleModal}<\/span>\n        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"modal-close is-large\"<\/span>\n        <span class=\"hljs-attr\">aria-label<\/span>=<span class=\"hljs-string\">\"close\"<\/span>\n      \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Note the way the props are passed down to the components from the parent as parameters. The <code>toggleModal<\/code> function is used to open and close the modal box as can be seen in both buttons at the top and bottom of the box. Since we have created just the Modal header, let\u2019s create the description, upload and save interface. Just after the closing header tag, create the description, upload and save features with:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">...\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">section<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"modal-card-body\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"columns\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"column\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"field\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"label\"<\/span>&gt;<\/span>Short Description<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"control\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">textarea<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"textarea\"<\/span>\n            <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{description}<\/span>\n            <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{handleDescriptionChange}<\/span>\n            <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Textarea\"<\/span>\n          \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n          <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{saveImage}<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"button is-info is-margin-top\"<\/span>\n        &gt;<\/span>\n          Save\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"column upload-column\"<\/span>&gt;<\/span>\n      {!preview ? (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n          <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{handleDropZoneClick}<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"button is-info is-margin-top\"<\/span>\n        &gt;<\/span>\n          Upload\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n      ) : (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{preview}<\/span> <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"Preview\"<\/span> \/&gt;<\/span>\n      )}\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>\n        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mainImageLoading\"<\/span>\n        <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">{{<\/span> <span class=\"hljs-attr\">display:<\/span> <span class=\"hljs-attr\">pending<\/span> ? '<span class=\"hljs-attr\">block<\/span>' <span class=\"hljs-attr\">:<\/span> '<span class=\"hljs-attr\">none<\/span>' }}\n      \/&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">section<\/span>&gt;<\/span>\n...\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>An input text area is used to handle description input with its value bound to <code>description<\/code> and an event listener in <code>onChange<\/code> is on hand to pick up any entries in the description field as specified in the <code>handleDescriptionChange<\/code> function. The <code>saveImage<\/code> function is assigned to a button which triggers once clicked. Now here\u2019s a tricky part, conditional rendering is used when displaying either the upload button or the uploaded image. The <code>pending<\/code> state comes in handy here and if <code>pending<\/code> is false (no image is uploaded), the upload button is visible and triggers the <code>handleDropZoneClick<\/code> when clicked. f <code>pending<\/code> is true, the uploaded image is displayed.<\/p>\n<p>Update the Modal.css file to <a href=\"https:\/\/raw.githubusercontent.com\/christiannwamba\/smart-search\/master\/src\/components\/Modal.css\">this<\/a>. Now that the user interface is done, let\u2019s create the server to handle uploads.<\/p>\n<h2>Configure Server.js<\/h2>\n<p>In the root directory of the project, create a file server.js, in here we will configure the server. In the server file, import all required dependencies with:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> Express = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'express'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> Webtask = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'webtask-tools'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> bodyParser = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'body-parser'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> cloudinary = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'cloudinary'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> multipart = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'connect-multiparty'<\/span>);\n<span class=\"hljs-keyword\">var<\/span> algoliasearch = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">'algoliasearch'<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>As stated earlier, we will be making use of an express server to make the process easier.  Let\u2019s configure the server to use the required modules with:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> app = Express();\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">bodyParser<\/span>.<span class=\"hljs-title\">urlencoded<\/span>({ <span class=\"hljs-title\">extended<\/span>: <span class=\"hljs-title\">false<\/span> }));\napp.<span class=\"hljs-keyword\">use<\/span>(<span class=\"hljs-title\">bodyParser<\/span>.<span class=\"hljs-title\">json<\/span>());\n<span class=\"hljs-comment\">\/\/ Multipart middleware to<\/span>\n<span class=\"hljs-comment\">\/\/ parse files<\/span>\n<span class=\"hljs-keyword\">const<\/span> multipartMiddleware = multipart();\n<span class=\"hljs-comment\">\/\/ Replace credentials<\/span>\n<span class=\"hljs-comment\">\/\/ with your Cloudinary credentials<\/span>\ncloudinary.config({\n  cloud_name: <span class=\"hljs-string\">'cloud_name'<\/span>,\n  api_key: <span class=\"hljs-string\">'key'<\/span>,\n  api_secret: <span class=\"hljs-string\">'secret'<\/span>\n});\n<span class=\"hljs-comment\">\/\/ Configure Algolia<\/span>\n<span class=\"hljs-comment\">\/\/ with your Algolia credentials<\/span>\n<span class=\"hljs-keyword\">var<\/span> algoliaClient = algoliasearch(\n  <span class=\"hljs-string\">'id'<\/span>,\n  <span class=\"hljs-string\">'key'<\/span>\n);\n<span class=\"hljs-keyword\">var<\/span> algoliaIndex = algoliaClient.initIndex(<span class=\"hljs-string\">'index'<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Images are uploaded via Cloudinary. Configure the upload API endpoint next with:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">app.post(<span class=\"hljs-string\">'\/upload'<\/span>, multipartMiddleware, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ Upload image to cloudinary<\/span>\n  cloudinary.v2.uploader.upload(\n    <span class=\"hljs-comment\">\/\/ File to upload<\/span>\n    req.files.image.path,\n    <span class=\"hljs-comment\">\/\/ AWS tagging transformation<\/span>\n    <span class=\"hljs-comment\">\/\/ Activate here by selecting a plan:<\/span>\n    <span class=\"hljs-comment\">\/\/ https:\/\/cloudinary.com\/console\/addons#aws_rek_tagging<\/span>\n    { <span class=\"hljs-attr\">categorization<\/span>: <span class=\"hljs-string\">'aws_rek_tagging'<\/span> },\n    <span class=\"hljs-comment\">\/\/ Callback function<\/span>\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">err, result<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">if<\/span> (err) <span class=\"hljs-keyword\">return<\/span> res.send(err);\n      res.json({ <span class=\"hljs-attr\">data<\/span>: result });\n    }\n  );\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The uploaded files are saved to an Algolia index where search and retrieval occurs. This makes use of a post request and you configure it with:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">app.post(<span class=\"hljs-string\">'\/save'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ index record<\/span>\n  <span class=\"hljs-built_in\">console<\/span>.log(req.body)\n  algoliaIndex.addObject(req.body, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">err, content<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">if<\/span> (err) <span class=\"hljs-keyword\">return<\/span> res.send(err);\n    res.json(content);\n  });\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Lastly, the app is exported to a Webtask with:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-built_in\">module<\/span>.exports = Webtask.fromExpress(app)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This creates a webtask for our server.<\/p>\n<p>Launch the app on the local server by running:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm run start\n<\/code><\/span><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-res.cloudinary.com\/image\/upload\/w_400,c_fill\/smart_search.gif\" alt=\"Smart Search\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"400\" height=\"272\"\/><\/p>\n<h2>Conclusion<\/h2>\n<p>Now we have a AI image search tool that searches  uploaded images and looks for a pattern queried using the Algolia search tool. <a href=\"https:\/\/cloudinary.com\/users\/register\/free\">Cloudinary<\/a>, which is used to upload images, provides a robust upload widget that makes it even better and improves the developer experience. Feel free to play around with the app, suggest improvements or make improvements to the source code <a href=\"https:\/\/github.com\/christiannwamba\/smart-search\">here<\/a>.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":21676,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,246],"class_list":["post-21675","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-react"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building a Smart AI Image Search Tool Using React Part 2<\/title>\n<meta name=\"description\" content=\"Learn to Building a Smart AI Image Search Tool Using React. We will be building the stateless components of our app.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Smart AI Image Search Tool Using React - Part 2: UI Components and Server Setup\" \/>\n<meta property=\"og:description\" content=\"Learn to Building a Smart AI Image Search Tool Using React. We will be building the stateless components of our app.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-02-07T17:45:18+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-03-30T20:57:58+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2-jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"1540\" \/>\n\t<meta property=\"og:image:height\" content=\"847\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"Building a Smart AI Image Search Tool Using React &#8211; Part 2: UI Components and Server Setup\",\"datePublished\":\"2018-02-07T17:45:18+00:00\",\"dateModified\":\"2025-03-30T20:57:58+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup\"},\"wordCount\":16,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"React\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2018\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup\",\"url\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup\",\"name\":\"Building a Smart AI Image Search Tool Using React Part 2\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA\",\"datePublished\":\"2018-02-07T17:45:18+00:00\",\"dateModified\":\"2025-03-30T20:57:58+00:00\",\"description\":\"Learn to Building a Smart AI Image Search Tool Using React. We will be building the stateless components of our app.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA\",\"width\":1540,\"height\":847},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Smart AI Image Search Tool Using React &#8211; Part 2: UI Components and Server Setup\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building a Smart AI Image Search Tool Using React Part 2","description":"Learn to Building a Smart AI Image Search Tool Using React. We will be building the stateless components of our app.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup","og_locale":"en_US","og_type":"article","og_title":"Building a Smart AI Image Search Tool Using React - Part 2: UI Components and Server Setup","og_description":"Learn to Building a Smart AI Image Search Tool Using React. We will be building the stateless components of our app.","og_url":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup","og_site_name":"Cloudinary Blog","article_published_time":"2018-02-07T17:45:18+00:00","article_modified_time":"2025-03-30T20:57:58+00:00","og_image":[{"width":1540,"height":847,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2-jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup"},"author":{"name":"","@id":""},"headline":"Building a Smart AI Image Search Tool Using React &#8211; Part 2: UI Components and Server Setup","datePublished":"2018-02-07T17:45:18+00:00","dateModified":"2025-03-30T20:57:58+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup"},"wordCount":16,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA","keywords":["Guest Post","React"],"inLanguage":"en-US","copyrightYear":"2018","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup","url":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup","name":"Building a Smart AI Image Search Tool Using React Part 2","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA","datePublished":"2018-02-07T17:45:18+00:00","dateModified":"2025-03-30T20:57:58+00:00","description":"Learn to Building a Smart AI Image Search Tool Using React. We will be building the stateless components of our app.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA","width":1540,"height":847},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/building_a_smart_ai_image_search_tool_using_react_part_2_ui_components_and_server_setup#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building a Smart AI Image Search Tool Using React &#8211; Part 2: UI Components and Server Setup"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649723568\/Web_Assets\/blog\/Building_a_Smart_AI_Image_Search_Part2\/Building_a_Smart_AI_Image_Search_Part2.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21675","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=21675"}],"version-history":[{"count":3,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21675\/revisions"}],"predecessor-version":[{"id":37329,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/21675\/revisions\/37329"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/21676"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=21675"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=21675"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=21675"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}