{"id":2776,"date":"2019-05-06T14:18:29","date_gmt":"2019-05-06T14:18:29","guid":{"rendered":"https:\/\/ionicframework.com\/?p=2776"},"modified":"2020-10-15T22:28:56","modified_gmt":"2020-10-15T22:28:56","slug":"build-secure-offline-apps-with-ionic-couchbase-lite","status":"publish","type":"post","link":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite","title":{"rendered":"Build Secure Offline Apps with Ionic &#038; Couchbase Lite"},"content":{"rendered":"<p><a href=\"https:\/\/www.couchbase.com\/products\/lite\" target=\"_blank\" rel=\"noopener\">Couchbase Lite<\/a> is a full-featured NoSQL database that runs locally on mobile devices. The <a href=\"https:\/\/ionicframework.com\/integrations\/couchbase-lite\" target=\"_blank\" rel=\"noopener\">Offline Storage plugin<\/a>, built and maintained by Ionic as part of <a href=\"https:\/\/ionicframework.com\/docs\/enterprise\" target=\"_blank\" rel=\"noopener\">Ionic Native<\/a>, makes it easy to take advantage of the Couchbase Lite database to create your application using an offline-first architecture. This allows you to offer your users a fast and seamless experience regardless of their connectivity at the time.<\/p>\n<blockquote><p>\n  New! View a live demo of Offline Storage <a href=\"https:\/\/ionicframework.com\/resources\/webinars\/offline-storage-build-secure-offline-first-apps\">here.<\/a> Complete documentation <a href=\"https:\/\/ionicframework.com\/docs\/enterprise\/offline-storage\">available here<\/a>.\n<\/p><\/blockquote>\n<p>In this article, I will demonstrate how to create an application supporting the full set of Create, Read, Update, and Delete (CRUD) operations. For simplicity, I will focus on the use of the database itself and will not get into more advanced topics such as data synchronization with a cloud-based API.<\/p>\n<p><!--more--><\/p>\n<h2>Demo Application<\/h2>\n<p>To demonstrate the power of the Offline Storage solution, I will use an application that displays different categories of tea. It allows the user to add new categories of tea, edit existing categories, and delete categories they no longer care about.<\/p>\n<p>The complete source code for this application is <a href=\"https:\/\/github.com\/ionic-team\/cs-demo-couchbase-lite\" target=\"_blank\" rel=\"noopener\">available here<\/a>.<\/p>\n<h2>Install Ionic Native<\/h2>\n<p>In order to use Ionic Native plugins, make sure you&#8217;re using the Ionic Enterprise Cordova CLI:<\/p>\n<pre><code class=\"language-shell\">npm uninstall -g cordova\nnpm install -g @ionic-enterprise\/cordova\n<\/code><\/pre>\n<p>Once you&#8217;ve installed the Ionic Enterprise Cordova CLI, you can register a native key, then install the Offline Storage plugin:<\/p>\n<pre><code class=\"language-shell\">ionic enterprise register\nionic cordova plugin add @ionic-enterprise\/offline-storage\n<\/code><\/pre>\n<p><strong>NOTE:<\/strong> Ionic Native includes a reliable set of Native APIs &amp; functionality that you can use in your Ionic app, quality controlled and maintained by the Ionic Team. <a href=\"https:\/\/ionicframework.com\/docs\/enterprise#sign-up-for-ionic-native-enterprise-edition\" target=\"_blank\" rel=\"noopener\">Sign up here.<\/a><\/p>\n<h2>Initialize a Database<\/h2>\n<h3>Create a Service<\/h3>\n<p>The first thing we will do is create a service, allowing us to abstract the data storage logic away from the rest of the pages and components in our application. Over time, this becomes easier to make changes to how data is stored without affecting the whole code base.<\/p>\n<p>We will just be storing tea categories with this application so we will create a single service called <code>TeaCategoriesService<\/code> which will handle all of the CRUD operations. For now, it will only use a Couchbase Lite database for storage and retrieval, but could easily be updated to include cloud-based storage in the future. Within an Ionic application, run the <code>generate service<\/code> command:<\/p>\n<pre><code class=\"language-bash\">ionic g service services\/tea-categories\/tea-categories\n&gt; ng generate service services\/tea-categories\/tea-categories\nCREATE src\/app\/services\/tea-categories\/tea-categories.service.spec.ts (369 bytes)\nCREATE src\/app\/services\/tea-categories\/tea-categories.service.ts (142 bytes)\n[OK] Generated service!\n<\/code><\/pre>\n<h3>Open the Database<\/h3>\n<p>The next step involves opening and initializing the database within <code>TeaCategoriesService<\/code>. The <code>initializeDatabase()<\/code> method below shows the basic steps required to open a database. The <code>readyPromise<\/code> is stored for use in other methods to ensure that the database has been initialized properly before we perform other operations.<\/p>\n<pre><code class=\"language-typescript\">import { Injectable } from &#039;@angular\/core&#039;;\n\nimport { Database, DatabaseConfiguration, IonicCBL, CordovaEngine } from &#039;ionic-enterprise-couchbase-lite&#039;;\n\n@Injectable({\n  providedIn: &#039;root&#039;\n})\nexport class TeaCategoriesService {\n  private readyPromise: Promise&lt;void&gt;;\n  private database: Database;\n\n  constructor() {\n    this.readyPromise = this.initializeDatabase();\n  }\n\n  private async initializeDatabase(): Promise&lt;void&gt; {\n    return new Promise((resolve) =&gt; {\n      IonicCBL.onReady(async () =&gt; {\n        const config = new DatabaseConfiguration();\n        config.setEncryptionKey(&#039;8e31f8f6-60bd-482a-9c70-69855dd02c38&#039;);\n        this.database = new Database(&#039;teacategories&#039;, config);\n        this.database.setEngine(new CordovaEngine({\n          allResultsChunkSize: 9999 \n        }));\n        await this.database.open();\n        console.log(\u201cDB Name: \u201c + this.database.getName());\n        console.log(\u201cDB Path: \u201c + await this.database.getPath());\n        resolve();\n      });\n    });\n  }\n}\n<\/code><\/pre>\n<p>Notice that no specific mobile platform has been mentioned in the code above. The plugin abstracts away those details from the Ionic web developer, providing a true cross-platform solution. To demonstrate this, build and run the application on a mobile device or in an emulator. Upon examination of the console log, you will see the iOS and Android-specific file paths to the newly created database:<\/p>\n<p>On iOS:<\/p>\n<pre><code class=\"language-console\">DB Name: teacategories\nDB Path: \/var\/mobile\/Containers\/Data\/Application\/EC31A8DD-863B-4894-BC64-B89A370377F9\/\nLibrary\/Application Support\/CouchbaseLite\/teacategories.cblite2\/\n<\/code><\/pre>\n<p>On Android:<\/p>\n<pre><code class=\"language-console\">DB Name: teacategories\nDB Path: \/data\/user\/0\/io.ionic.cs_demo_couchbase_lite\/files\/teacategories.cblite2\/\n<\/code><\/pre>\n<h2>Storing Data<\/h2>\n<h3>TeaCategory Model<\/h3>\n<p>A generic data model &#8211; <code>TeaCategory<\/code> &#8211; is used to communicate between the <code>TeaCategoriesService<\/code> and its consumers. This allows us to decouple the data from the actual storage mechanism, making the application more maintainable as we add features in the future.<\/p>\n<pre><code class=\"language-typescript\">export interface TeaCategory {\n  id?: string;\n  name: string;\n  description: string;\n}\n<\/code><\/pre>\n<p>The \u201cid\u201d property is optional since newly created objects will not have any ID until they are added to the database.<\/p>\n<h3>Back to the TeaCategoriesService<\/h3>\n<p>When adding a new document to the database, we create a <code>MutableDocument<\/code> object. As the name implies, this is a document object that can be changed. After the object is created, set the properties we are concerned with and save the document:<\/p>\n<pre><code class=\"language-typescript\">  private async add(category: TeaCategory): Promise&lt;void&gt; {\n    await this.readyPromise;\n    const doc = new MutableDocument()\n      .setString(&#039;name&#039;, category.name)\n      .setString(&#039;description&#039;, category.description);\n    return this.database.save(doc);\n  }\n<\/code><\/pre>\n<p>Notice that the <code>id<\/code> property is not set &#8211; it will be automatically assigned by the database.<\/p>\n<h3>TeaCategoryEditorPage<\/h3>\n<p>The <code>TeaCategoryEditorPage<\/code> needs to pass the appropriate data to the service, await the completion of the save, and then navigate back to the <code>HomePage<\/code>:<\/p>\n<pre><code class=\"language-typescript\">  async save() {\n    await this.teaCategories.save({\n      name: this.name,\n      description: this.description\n    });\n\n    this.navController.back();\n  }\n<\/code><\/pre>\n<p><a href=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory.png\"><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory.png\" alt=\"\" class=\"aligncenter size-full wp-image-2780 lazyload\" data-srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory-1024x1024.png 1024w\" data-sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" style=\"--smush-placeholder-width: 2000px; --smush-placeholder-aspect-ratio: 2000\/2000;\" \/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory.png\" alt=\"\" class=\"aligncenter size-full wp-image-2780\" srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-addTeaCategory-1024x1024.png 1024w\" sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" \/><\/noscript><\/a><\/p>\n<h3>Querying Documents<\/h3>\n<p>Now that we are able to create documents, we need to be able to display them on the app\u2019s  <code>HomePage<\/code>. In order to retrieve all of the documents from the database, we can build a query that returns the data we need, execute the query, and then unpack the data into the generic <code>TeaCategory<\/code> model that we use to pass data back and forth.<\/p>\n<h3>TeaCategoryService: GetAll()<\/h3>\n<p>The bulk of the work is performed by the service, which returns a promise that resolves to an array of tea categories. This allows us to hide the details of the storage mechanism from the consumers of the service.<\/p>\n<pre><code class=\"language-typescript\">  async getAll(): Promise&lt;Array&lt;TeaCategory&gt;&gt; {\n    await this.readyPromise;\n    const query = QueryBuilder.select(\n      SelectResult.property(&#039;name&#039;),\n      SelectResult.property(&#039;description&#039;),\n      SelectResult.expression(Meta.id)\n    ).from(DataSource.database(this.database))\n     .orderBy(Ordering.property(&#039;name&#039;));\n\n    const ret = await query.execute();\n    const res = await ret.allResults();\n\n    return res.map(t =&gt; ({\n      id: t._id,\n      name: t.name,\n      description: t.description\n    }));\n  }\n<\/code><\/pre>\n<h3>TeaCategoryEditorPage: Retrieve Results<\/h3>\n<p>The <code>TeaCategoryEditorPage<\/code> page just needs to await the results of the query.<\/p>\n<pre><code class=\"language-typescript\">  async ngOnInit() {\n    this.categories = await this.teaCategories.getAll();\n  }\n<\/code><\/pre>\n<p><a href=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories.png\"><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories.png\" alt=\"\" class=\"aligncenter size-full wp-image-2781 lazyload\" data-srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories-1024x1024.png 1024w\" data-sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" style=\"--smush-placeholder-width: 2000px; --smush-placeholder-aspect-ratio: 2000\/2000;\" \/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories.png\" alt=\"\" class=\"aligncenter size-full wp-image-2781\" srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-teaCategories-1024x1024.png 1024w\" sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" \/><\/noscript><\/a><\/p>\n<h2>Updating Tea Category Documents<\/h2>\n<p>In order to update the tea category documents, the <code>TeaCategoryEditorPage<\/code> needs to obtain the document to edit then needs to save the changes back to the database.<\/p>\n<h3>TeaCategoryService: Get Document<\/h3>\n<p>The <code>get<\/code> routine retrieves the document based on <code>id<\/code> and unpacks the document into the model we are using to represent the data.<\/p>\n<pre><code class=\"language-typescript\">  async get(id: string): Promise&lt;TeaCategory&gt; {\n    await this.readyPromise;\n    const d = await this.database.getDocument(id);\n    const dict = d.toDictionary();\n\n    return {\n      id: d.getId(),\n      name: dict.name,\n      description: dict.description\n    };\n  }\n<\/code><\/pre>\n<p>We do not want developers that are using the <code>TeaCategoryService<\/code> to worry about whether they are performing an insert or an update. They can just pass along a <code>TeaCategory<\/code> object that needs to be saved and the service can figure out if the operation is an \u201cadd\u201d or an \u201cupdate\u201d based on whether or not the object has an ID.<\/p>\n<pre><code class=\"language-typescript\"> async save(category: TeaCategory): Promise&lt;void&gt; {\n    return category.id ? this.update(category) : this.add(category);\n  }\n\n  private async add(category: TeaCategory): Promise&lt;void&gt; {\n    await this.readyPromise;\n    const doc = new MutableDocument()\n      .setString(&#039;name&#039;, category.name)\n      .setString(&#039;description&#039;, category.description);\n\n    return this.database.save(doc);\n  }\n\n  private async update(category: TeaCategory): Promise&lt;void&gt; {\n    await this.readyPromise;\n    const d = await this.database.getDocument(category.id);\n    const md = new MutableDocument(d.getId(), d.getSequence(), d.getData());\n    md.setString(&#039;name&#039;, category.name);\n    md.setString(&#039;description&#039;, category.description);\n\n    return this.database.teaCatgories.save(md);\n  }\n<\/code><\/pre>\n<h3>TeaCategoryEditorPage: Making Changes<\/h3>\n<p>Next, the <code>TeaCategoryEditorPage<\/code> can easily handle both adding new tea categories and making changes to existing tea categories:<\/p>\n<pre><code class=\"language-typescript\">  async ngOnInit() {\n    const id = this.route.snapshot.paramMap.get(&#039;id&#039;);\n    if (id) {\n      this.title = &#039;Edit Tea Category&#039;;\n      const category = await this.teaCategories.get(id);\n      this.id = category.id;\n      this.name = category.name;\n      this.description = category.description;\n    } else {\n      this.title = &#039;Add New Tea Category&#039;;\n    }\n  }\n\n  async save() {\n    await this.teaCategories.save({\n      id: this.id,\n      name: this.name,\n      description: this.description\n    });\n\n    this.navController.back();\n  }\n<\/code><\/pre>\n<p><a href=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory.png\"><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory.png\" alt=\"\" class=\"aligncenter size-full wp-image-2782 lazyload\" data-srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory-1024x1024.png 1024w\" data-sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" style=\"--smush-placeholder-width: 2000px; --smush-placeholder-aspect-ratio: 2000\/2000;\" \/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory.png\" alt=\"\" class=\"aligncenter size-full wp-image-2782\" srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-EditTeaCategory-1024x1024.png 1024w\" sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" \/><\/noscript><\/a><\/p>\n<h2>Responding to Changes<\/h2>\n<p>Our users can now add, update, and view tea categories, but the <code>HomePage<\/code> does not show the changes right away. Instead, it only shows the changes after the user closes the application and starts it up again.<\/p>\n<p>Furthermore, if the application had a process that would get new tea categories from a cloud-based service then update the database accordingly, we would not see those changes either.<\/p>\n<p>So, we need a way for the application to respond to changes in the database.<\/p>\n<h3>TeaCategoryService: Respond to Data Changes<\/h3>\n<p>The database allows us to add change listeners in order to respond to changes to tea category data. We will again use our <code>TeaCategoryService<\/code> to create an abstraction layer between the database and the rest of our code.<\/p>\n<pre><code class=\"language-typescript\">  onChange(cb: () =&gt; void) {\n    this.readyPromise\n      .then(() =&gt; this.database.addChangeListener(cb));\n  }\n<\/code><\/pre>\n<h3>HomePage: Detecting Database Changes<\/h3>\n<p>In the <code>HomePage<\/code>, we still need to fetch the tea categories on entry, but we will also fetch the tea categories each time that a change to the database is detected.<\/p>\n<p>Move ngOnInit() logic to a private method<\/p>\n<pre><code class=\"language-typescript\">  private async fetchCategories(): Promise&lt;void&gt; {\n    this.categories = await this.teaCategories.getAll();\n  }\n<\/code><\/pre>\n<p>In ngOnInit(), call the method and then set it up to be called with each database change:<\/p>\n<pre><code class=\"language-typescript\">  ngOnInit() {\n    this.fetchCategories();\n    this.teaCategories.onChange(() =&gt; this.fetchCategories());\n  }\n<\/code><\/pre>\n<h2>Deleting a Document<\/h2>\n<p>The final CRUD operation is the deletion of documents.<\/p>\n<h3>TeaCategoryService: Delete<\/h3>\n<p>In order to delete a document, we first get the document using the ID and then tell the database to delete the document.<\/p>\n<pre><code class=\"language-typescript\">  async delete(id: string): Promise&lt;void&gt;{\n    await this.readyPromise;\n    const d = await this.database.getDocument(id);\n\n    return this.database.deleteDocument(d);\n  }\n<\/code><\/pre>\n<h3>HomePage: Delete UI<\/h3>\n<p>The <code>HomePage<\/code> page\u2019s responsibility here is to confirm that the user does intend to delete the category. If so, it hands the ID off to the service to do the actual work.<\/p>\n<pre><code class=\"language-typescript\">  async removeTeaCategory(id: string): Promise&lt;void&gt; {\n    const alert = await this.alertController.create({\n      header: &#039;Confirm Delete&#039;,\n      message: &#039;Are you sure you want to permanently remove this category?&#039;,\n      buttons: [\n        { text: &#039;Yes&#039;, handler: () =&gt; this.teaCategories.delete(id) },\n        { text: &#039;No&#039;, role: &#039;cancel&#039; }\n      ]\n    });\n    alert.present();\n  }\n<\/code><\/pre>\n<p><a href=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete.png\"><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete.png\" alt=\"\" class=\"aligncenter size-full wp-image-2783 lazyload\" data-srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete-1024x1024.png 1024w\" data-sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" style=\"--smush-placeholder-width: 2000px; --smush-placeholder-aspect-ratio: 2000\/2000;\" \/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete.png\" alt=\"\" class=\"aligncenter size-full wp-image-2783\" srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-ConfirmDelete-1024x1024.png 1024w\" sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" \/><\/noscript><\/a><\/p>\n<p>After the user confirms the deletion, Android Oil is removed from the Tea Category list:<\/p>\n<p><a href=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete.png\"><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" data-src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete.png\" alt=\"\" class=\"aligncenter size-full wp-image-2784 lazyload\" data-srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete-1024x1024.png 1024w\" data-sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" src=\"data:image\/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\" style=\"--smush-placeholder-width: 2000px; --smush-placeholder-aspect-ratio: 2000\/2000;\" \/><noscript><img loading=\"lazy\" decoding=\"async\" width=\"2000\" height=\"2000\" src=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete.png\" alt=\"\" class=\"aligncenter size-full wp-image-2784\" srcset=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete.png 2000w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete-150x150.png 150w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete-300x300.png 300w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete-768x768.png 768w, https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/cbl-AfterDelete-1024x1024.png 1024w\" sizes=\"auto, (max-width: 2000px) 100vw, 2000px\" \/><\/noscript><\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>In this article, we explored the Ionic Native Offline Storage plugin\u2019s complete offline experience by implementing the full set of CRUD operations available. We also explored best practices by architecting our application to separate data storage concerns into a separate service class. This shields the rest of our application from being concerned with details about how the data is stored and will allow us to easily expand our application in the future to use features such as synchronizing our offline data with a cloud-based data service. These are just some of the scenarios that can be supported in your application using the Offline Storage plugin.<\/p>\n<p>If you are interested in exploring how Ionic Native can benefit your application development and aid you in delivering the best experience to your users, please contact one of our <a href=\"https:\/\/ionicframework.com\/strategysession\" target=\"_blank\" rel=\"noopener\">Solutions Architects<\/a> (like me!) to schedule a demonstration.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Couchbase Lite is a full-featured NoSQL database that runs locally on mobile devices. The Offline Storage plugin, built and maintained by Ionic as part of Ionic Native, makes it easy to take advantage of the Couchbase Lite database to create your application using an offline-first architecture. This allows you to offer your users a fast [&hellip;]<\/p>\n","protected":false},"author":55,"featured_media":2778,"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":[122],"tags":[150,27],"class_list":["post-2776","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-product","tag-couchbase-lite","tag-ionic-native"],"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>Build Secure Offline Apps with Ionic &amp; Couchbase Lite - 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\/build-secure-offline-apps-with-ionic-couchbase-lite\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build Secure Offline Apps with Ionic &amp; Couchbase Lite\" \/>\n<meta property=\"og:description\" content=\"Couchbase Lite is a full-featured NoSQL database that runs locally on mobile devices. The Offline Storage plugin, built and maintained by Ionic as part of Ionic Native, makes it easy to take advantage of the Couchbase Lite database to create your application using an offline-first architecture. This allows you to offer your users a fast [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite\" \/>\n<meta property=\"og:site_name\" content=\"Ionic Blog\" \/>\n<meta property=\"article:published_time\" content=\"2019-05-06T14:18:29+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2020-10-15T22:28:56+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1440\" \/>\n\t<meta property=\"og:image:height\" content=\"800\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Ken Sodemann\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:site\" content=\"@ionicframework\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Ken Sodemann\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#article\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite\"},\"author\":{\"name\":\"Ken Sodemann\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/00deec3725c91ec95772de89be49f5ca\"},\"headline\":\"Build Secure Offline Apps with Ionic &#038; Couchbase Lite\",\"datePublished\":\"2019-05-06T14:18:29+00:00\",\"dateModified\":\"2020-10-15T22:28:56+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite\"},\"wordCount\":1307,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/ionic.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png\",\"keywords\":[\"couchbase lite\",\"Ionic Native\"],\"articleSection\":[\"Product\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite\",\"url\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite\",\"name\":\"Build Secure Offline Apps with Ionic & Couchbase Lite - Ionic Blog\",\"isPartOf\":{\"@id\":\"https:\/\/ionic.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#primaryimage\"},\"image\":{\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png\",\"datePublished\":\"2019-05-06T14:18:29+00:00\",\"dateModified\":\"2020-10-15T22:28:56+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#primaryimage\",\"url\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png\",\"contentUrl\":\"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png\",\"width\":1440,\"height\":800},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/ionic.io\/blog\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Build Secure Offline Apps with Ionic &#038; Couchbase Lite\"}]},{\"@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\/00deec3725c91ec95772de89be49f5ca\",\"name\":\"Ken Sodemann\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/a9bac28efdb8aadafd3c99d5a39a73a67712b5bd63aa6084c138ee7c157f31f1?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/a9bac28efdb8aadafd3c99d5a39a73a67712b5bd63aa6084c138ee7c157f31f1?s=96&d=mm&r=g\",\"caption\":\"Ken Sodemann\"},\"url\":\"https:\/\/ionic.io\/blog\/author\/ken_sodemann\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Build Secure Offline Apps with Ionic & Couchbase Lite - 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\/build-secure-offline-apps-with-ionic-couchbase-lite","og_locale":"en_US","og_type":"article","og_title":"Build Secure Offline Apps with Ionic & Couchbase Lite","og_description":"Couchbase Lite is a full-featured NoSQL database that runs locally on mobile devices. The Offline Storage plugin, built and maintained by Ionic as part of Ionic Native, makes it easy to take advantage of the Couchbase Lite database to create your application using an offline-first architecture. This allows you to offer your users a fast [&hellip;]","og_url":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite","og_site_name":"Ionic Blog","article_published_time":"2019-05-06T14:18:29+00:00","article_modified_time":"2020-10-15T22:28:56+00:00","og_image":[{"width":1440,"height":800,"url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png","type":"image\/png"}],"author":"Ken Sodemann","twitter_card":"summary_large_image","twitter_creator":"@ionicframework","twitter_site":"@ionicframework","twitter_misc":{"Written by":"Ken Sodemann","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#article","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite"},"author":{"name":"Ken Sodemann","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/00deec3725c91ec95772de89be49f5ca"},"headline":"Build Secure Offline Apps with Ionic &#038; Couchbase Lite","datePublished":"2019-05-06T14:18:29+00:00","dateModified":"2020-10-15T22:28:56+00:00","mainEntityOfPage":{"@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite"},"wordCount":1307,"commentCount":0,"publisher":{"@id":"https:\/\/ionic.io\/blog\/#organization"},"image":{"@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png","keywords":["couchbase lite","Ionic Native"],"articleSection":["Product"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#respond"]}]},{"@type":"WebPage","@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite","url":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite","name":"Build Secure Offline Apps with Ionic & Couchbase Lite - Ionic Blog","isPartOf":{"@id":"https:\/\/ionic.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#primaryimage"},"image":{"@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#primaryimage"},"thumbnailUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png","datePublished":"2019-05-06T14:18:29+00:00","dateModified":"2020-10-15T22:28:56+00:00","breadcrumb":{"@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#primaryimage","url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png","contentUrl":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png","width":1440,"height":800},{"@type":"BreadcrumbList","@id":"https:\/\/ionic.io\/blog\/build-secure-offline-apps-with-ionic-couchbase-lite#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ionic.io\/blog"},{"@type":"ListItem","position":2,"name":"Build Secure Offline Apps with Ionic &#038; Couchbase Lite"}]},{"@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\/00deec3725c91ec95772de89be49f5ca","name":"Ken Sodemann","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/ionic.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/a9bac28efdb8aadafd3c99d5a39a73a67712b5bd63aa6084c138ee7c157f31f1?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/a9bac28efdb8aadafd3c99d5a39a73a67712b5bd63aa6084c138ee7c157f31f1?s=96&d=mm&r=g","caption":"Ken Sodemann"},"url":"https:\/\/ionic.io\/blog\/author\/ken_sodemann"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/ionic.io\/blog\/wp-content\/uploads\/2019\/05\/couchbase-ionic.png","_links":{"self":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/2776","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\/55"}],"replies":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/comments?post=2776"}],"version-history":[{"count":0,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/posts\/2776\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media\/2778"}],"wp:attachment":[{"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/media?parent=2776"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/categories?post=2776"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ionic.io\/blog\/wp-json\/wp\/v2\/tags?post=2776"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}