{"id":3044,"date":"2020-01-07T20:19:56","date_gmt":"2020-01-07T20:19:56","guid":{"rendered":"https:\/\/ionicframework.com\/blog\/?p=3044"},"modified":"2020-01-07T20:19:56","modified_gmt":"2020-01-07T20:19:56","slug":"firebase-hooks-with-ionic-react","status":"publish","type":"post","link":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react","title":{"rendered":"Firebase Hooks with Ionic React"},"content":{"rendered":"<blockquote><p>\n  This is a guest post from Aaron Saunders. Aaron is a developer and CEO at <a href=\"https:\/\/www.clearlyinnovative.com\/\">Clearly Innovative<\/a>. This is the first of a series of posts Aaron wrote about using Firebase and React hooks with Ionic React.\n<\/p><\/blockquote>\n<p>In this post, I&#8217;ll walk through the process of creating a custom hook that uploads a file to Firebase.<\/p>\n<p>Since the focus of the post is the custom hook, I will focus on pieces of code related to the hook, how it is called, and how it is implemented, not the surrounding code. However, the complete source code for the project is provided <a href=\"https:\/\/github.com\/aaronksaunders\/simple-file-upload-hook\/\">here<\/a>.<\/p>\n<p><!--more--><\/p>\n<h2>Setting Up Parent Component<\/h2>\n<p>We need to make sure we set things up by initializing the custom file upload hook <code>useFirebaseUpload<\/code>:<\/p>\n<pre><code class=\"language-typescript\">\/\/ custom hook that will upload to firebase\nimport useFirebaseUpload from &quot;..\/hooks\/useFirebaseUpload&quot;;\n\n\n\/\/ setting up the hook to upload file and track its progress\nconst [ { data, isLoading, isError, progress }, setFileData ] = useFirebaseUpload();\n<\/code><\/pre>\n<p>Next, in the parent component, we want to present any errors that are generated and get progress information when the file is being uploaded from the custom file upload hook <code>useFirebaseUpload<\/code>. The following properties are all reactive and provided by the custom hook: <code>isError<\/code>, <code>isLoading<\/code> and <code>progress<\/code>.<\/p>\n<pre><code class=\"language-typescript\">&lt;IonContent&gt;\n  {\/* get error from hook and display if necessary *\/}\n  {isError &amp;&amp; &lt;div&gt;ERROR: {isError.message}&lt;\/div&gt;}\n\n  {\/* get loading info from hook &amp; display progress if necessary *\/}\n  {isLoading &amp;&amp; progress &amp;&amp; (\n    &lt;IonProgressBar value={progress.value}&gt;&lt;\/IonProgressBar&gt;\n  ) }\n&lt;\/IonContent&gt;\n<\/code><\/pre>\n<p>The last missing piece for the parent component is selecting the file and then calling the method on the custom Firebase hook to upload the file. We handle that with the code listed below.<\/p>\n<p>Calling that function will set a property in the hook that is a dependency for the <code>useEffects<\/code> handler we set that actually triggers the Firebase upload to start.<\/p>\n<pre><code class=\"language-typescript\">{\/* user selects a file and returns the info required for upload *\/}\n  &lt;input\n    type=&quot;file&quot;\n    onChange={(e: any) =&gt; setFileData(e.target.files[0])}\n  \/&gt;\n<\/code><\/pre>\n<h2>Inside Custom Firebase File Upload Hook<\/h2>\n<p>We will initialize Firebase at the start of the component function, and define a reference to the storage to be used throughout the component function.<\/p>\n<blockquote><p>\n  To set up Firebase, be sure to refer to the <a href=\"https:\/\/firebase.google.com\/docs\/web\/setup\">Firebase docs<\/a>.\n<\/p><\/blockquote>\n<pre><code class=\"language-typescript\">import { useState, useEffect } from &quot;react&quot;;\nimport firebase from &quot;firebase&quot;;\n\nvar firebaseConfig = {\n\/\/ ADD YOUR FIREBASE CONFIGURATION\n};\n\n\/\/ Initialize Firebase\nfirebase.initializeApp(firebaseConfig);\n\n\/\/ the firebase reference to storage\nconst storageRef = firebase.storage().ref();\n<\/code><\/pre>\n<p>Since we are using <code>typescript<\/code>, we need to define some interfaces for use in the hook then we define the return type from the hook function<\/p>\n<pre><code class=\"language-javascript\">interface UploadDataResponse { metaData: firebase.storage.FullMetadata, downloadUrl: any };\ninterface ProgressResponse { value: number }\n\nfunction FirebaseFileUploadApi(): [{\n    data: UploadDataResponse | undefined,\n    isLoading: boolean,\n    isError: any,\n    progress: ProgressResponse | null\n},\n    Function\n] { \/\/additional code... }\n<\/code><\/pre>\n<p>Next, we start to define the state variables needed by the hook.<\/p>\n<pre><code class=\"language-javascript\">\/\/ the data from the firebase file upload response\nconst [data, setData] = useState&lt;UploadDataResponse | undefined&gt;();\n\n\/\/ sets properties on the file to be uploaded, this is called\n\/\/ by the parent component\nconst [fileData, setFileData] = useState&lt;File | null&gt;();\n\n\/\/ if we are loading a file or not\nconst [isLoading, setIsLoading] = useState&lt;boolean&gt;(false);\n\n\/\/ if an error happened during the process\nconst [isError, setIsError] = useState&lt;any&gt;(false);\n\n\/\/ used for tracking the % of upload completed\nconst [progress, setProgress] = useState&lt;ProgressResponse | null&gt;(null);\n<\/code><\/pre>\n<h2>The <code>useEffect<\/code> handler<\/h2>\n<p><code>useEffect<\/code> is called after every render of the component. You can control the rendering by providing an array of dependencies as the second parameter.<\/p>\n<p>With our hook, we only want it to be called when the <code>fileData<\/code> property changes. Meaning that the user has selected a file to upload and indicated that by calling the <code>setData<\/code> method.<\/p>\n<pre><code class=\"language-javascript\">\/\/ this function will be called when the any properties in the dependency array changes\nuseEffect(() =&gt; {\n    const uploadData = async () =&gt; {\n        \/\/ initialize upload information\n        setIsError(false);\n        setIsLoading(true);\n\n        setProgress({ value: 0 });\n\n        if (!fileData) return;\n\n        \/\/ wrap in a try catch block to update the error state\n        try {\n            let fName = `${(new Date()).getTime()}-${fileData.name}`\n\n            \/\/ setting the firebase properties for the file upload\n            let ref = storageRef.child(&quot;images\/&quot; + fName);\n            let uploadTask = ref.put(fileData);\n\n            \/\/ tracking the state of the upload to assist in updating the\n            \/\/ application UI\n            \/\/\n            \/\/ method details covered in the next section...\n            uploadTask.on(\n                firebase.storage.TaskEvent.STATE_CHANGED,\n                _progress =&gt; { },\n                _error =&gt; { },\n                async () =&gt; { }\n            );\n        } catch (_error) {\n            setIsLoading(false);\n            setIsError(_error);\n        }\n    };\n\n    fileData &amp;&amp; uploadData();\n}, [fileData]);\n\n<\/code><\/pre>\n<h2>Manage Firebase File Upload State Changes<\/h2>\n<p>The call to upload the file, <code>ref.put(fileData)<\/code>, returns a property that we can use to monitor the state of the upload.<br \/>\nThis could be the current upload progress, any errors, or when the upload has completed.<br \/>\nWe have included a handler for each one and set the appropriate state variable to be accessible from the hook.<br \/>\nFor the completion handler, we&#8217;ll have to dig deeper since we need to make another call to Firebase using <code>uploadTask.snapshot.ref.getDownloadURL()<\/code>. This will return the <code>downloadUrl<\/code> which is needed to render the image in the application.<\/p>\n<pre><code class=\"language-typescript\">\/\/ tracking the state of the upload to assist in updating the\n\/\/ application UI\n\nuploadTask.on(\n    firebase.storage.TaskEvent.STATE_CHANGED,\n    _progress =&gt; {\n        var value =\n            (_progress.bytesTransferred \/ _progress.totalBytes);\n        console.log(&quot;Upload is &quot; + value * 100 + &quot;% done&quot;);\n        setProgress({ value });\n    },\n    _error =&gt; {\n        setIsLoading(false);\n        setIsError(_error);\n    },\n    \/\/ completion handler\n    async () =&gt; {\n        setIsError(false);\n        setIsLoading(false);\n\n        \/\/ need to get the url to download the file\n        let downloadUrl = await uploadTask.snapshot.ref.getDownloadURL();\n\n        \/\/ set the data when upload has completed\n        setData({\n            metaData: uploadTask.snapshot.metadata,\n            downloadUrl\n        });\n\n        \/\/ reset progress\n        setProgress(null);\n    }\n);\n\n<\/code><\/pre>\n<h2>Wrapping Up<\/h2>\n<p>This was a very basic example of how to upload files to Firebase using React Hooks. I have created a separate GitHub repo for this project but have excluded other features such as authentication and account creation. I felt it was important to keep the code simple.<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/aaronksaunders\/simple-file-upload-hook\">Firebase File Upload Hook<\/a><\/li>\n<\/ul>\n<p>Enjoy! Until next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a guest post from Aaron Saunders. Aaron is a developer and CEO at Clearly Innovative. This is the first of a series of posts Aaron wrote about using Firebase and React hooks with Ionic React. In this post, I&#8217;ll walk through the process of creating a custom hook that uploads a file to [&hellip;]<\/p>\n","protected":false},"author":71,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"publish_to_discourse":"","publish_post_category":"","wpdc_auto_publish_overridden":"","wpdc_topic_tags":"","wpdc_pin_topic":"","wpdc_pin_until":"","discourse_post_id":"","discourse_permalink":"","wpdc_publishing_response":"","wpdc_publishing_error":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1,121,124],"tags":[73,136,149,25],"class_list":["post-3044","post","type-post","status-publish","format-standard","hentry","category-all","category-engineering","category-tutorials","tag-firebase","tag-react","tag-react-hooks","tag-tutorials"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v23.0 (Yoast SEO v23.0) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Firebase Hooks with Ionic React - Ionic Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Firebase Hooks with Ionic React\" \/>\n<meta property=\"og:description\" content=\"This is a guest post from Aaron Saunders. Aaron is a developer and CEO at Clearly Innovative. This is the first of a series of posts Aaron wrote about using Firebase and React hooks with Ionic React. In this post, I&#8217;ll walk through the process of creating a custom hook that uploads a file to [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react\" \/>\n<meta property=\"og:site_name\" content=\"Ionic Blog\" \/>\n<meta property=\"article:published_time\" content=\"2020-01-07T20:19:56+00:00\" \/>\n<meta name=\"author\" content=\"Aaron Saunders\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@aaronksaunders\" \/>\n<meta name=\"twitter:site\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Aaron Saunders\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react#article\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react\"},\"author\":{\"name\":\"Aaron Saunders\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/b1a7d2e582d3520f6fe74c875bdbb7c8\"},\"headline\":\"Firebase Hooks with Ionic React\",\"datePublished\":\"2020-01-07T20:19:56+00:00\",\"dateModified\":\"2020-01-07T20:19:56+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react\"},\"wordCount\":519,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"keywords\":[\"firebase\",\"react\",\"react hooks\",\"Tutorials\"],\"articleSection\":[\"All\",\"Engineering\",\"Tutorials\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react\",\"url\":\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react\",\"name\":\"Firebase Hooks with Ionic React - Ionic Blog\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/#website\"},\"datePublished\":\"2020-01-07T20:19:56+00:00\",\"dateModified\":\"2020-01-07T20:19:56+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/ionic.io\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Firebase Hooks with Ionic React\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/ionic.io\/blog\/#website\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"name\":\"ionic.io\/blog\",\"description\":\"Build amazing native and progressive web apps with the web\",\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/ionic.io\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/ionic.io\/blog\/#organization\",\"name\":\"Ionic\",\"url\":\"https:\/\/ionic.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png\",\"width\":1920,\"height\":854,\"caption\":\"Ionic\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/ionicframework\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/b1a7d2e582d3520f6fe74c875bdbb7c8\",\"name\":\"Aaron Saunders\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/12\/aaron-s-150x150.jpg\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/12\/aaron-s-150x150.jpg\",\"caption\":\"Aaron Saunders\"},\"sameAs\":[\"https:\/\/www.clearlyinnovative.com\/\",\"https:\/\/x.com\/aaronksaunders\"],\"url\":\"https:\/\/ionic.io\/blog\/author\/aaronksaunders\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Firebase Hooks with Ionic React - Ionic Blog","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:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react","og_locale":"en_US","og_type":"article","og_title":"Firebase Hooks with Ionic React","og_description":"This is a guest post from Aaron Saunders. Aaron is a developer and CEO at Clearly Innovative. This is the first of a series of posts Aaron wrote about using Firebase and React hooks with Ionic React. In this post, I&#8217;ll walk through the process of creating a custom hook that uploads a file to [&hellip;]","og_url":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react","og_site_name":"Ionic Blog","article_published_time":"2020-01-07T20:19:56+00:00","author":"Aaron Saunders","twitter_card":"summary_large_image","twitter_creator":"@aaronksaunders","twitter_site":"@ionicframework","twitter_misc":{"Written by":"Aaron Saunders","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react#article","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react"},"author":{"name":"Aaron Saunders","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/b1a7d2e582d3520f6fe74c875bdbb7c8"},"headline":"Firebase Hooks with Ionic React","datePublished":"2020-01-07T20:19:56+00:00","dateModified":"2020-01-07T20:19:56+00:00","mainEntityOfPage":{"@id":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react"},"wordCount":519,"commentCount":0,"publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"keywords":["firebase","react","react hooks","Tutorials"],"articleSection":["All","Engineering","Tutorials"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react","url":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react","name":"Firebase Hooks with Ionic React - Ionic Blog","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/#website"},"datePublished":"2020-01-07T20:19:56+00:00","dateModified":"2020-01-07T20:19:56+00:00","breadcrumb":{"@id":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/ionic.io\/blog\/firebase-hooks-with-ionic-react#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ionic.io\/blog"},{"@type":"ListItem","position":2,"name":"Firebase Hooks with Ionic React"}]},{"@type":"WebSite","@id":"https:\/\/ionic.io\/blog\/#website","url":"https:\/\/ionic.io\/blog\/","name":"ionic.io\/blog","description":"Build amazing native and progressive web apps with the web","publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ionic.io\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/ionic.io\/blog\/#organization","name":"Ionic","url":"https:\/\/ionic.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2020\/10\/white-on-color.png","width":1920,"height":854,"caption":"Ionic"},"image":{"@id":"https:\/\/ionic.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/ionicframework"]},{"@type":"Person","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/b1a7d2e582d3520f6fe74c875bdbb7c8","name":"Aaron Saunders","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/12\/aaron-s-150x150.jpg","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/12\/aaron-s-150x150.jpg","caption":"Aaron Saunders"},"sameAs":["https:\/\/www.clearlyinnovative.com\/","https:\/\/x.com\/aaronksaunders"],"url":"https:\/\/ionic.io\/blog\/author\/aaronksaunders"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3044","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/users\/71"}],"replies":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/comments?post=3044"}],"version-history":[{"count":0,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/3044\/revisions"}],"wp:attachment":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media?parent=3044"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/categories?post=3044"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/tags?post=3044"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}