{"id":385,"date":"2025-02-26T16:00:00","date_gmt":"2025-02-26T16:00:00","guid":{"rendered":"https:\/\/imcodinggenius.com\/?p=385"},"modified":"2025-02-26T16:00:00","modified_gmt":"2025-02-26T16:00:00","slug":"mongodb-with-kotlin-coroutines-in-ktor","status":"publish","type":"post","link":"https:\/\/imcodinggenius.com\/?p=385","title":{"rendered":"MongoDB with Kotlin Coroutines in Ktor"},"content":{"rendered":"<p>In this article, I will show you how to work with MongoDB and Kotlin coroutines in a Ktor application. <\/p>\n<p>Note: this article was created based on one of the services we implement in my <a href=\"https:\/\/codersee.com\/courses\/ktor-server-pro\/\">Ktor Server Pro course<\/a>. If you would like to learn how to expose a fully functional REST API, secure the application, and many many more, then don\u2019t hesitate and secure your spot today<\/p>\n<p>Before we start, I just wanted to mention that at the moment of writing <strong>KMongo is officially deprecated! <\/strong>Maybe you saw my previous article, maybe you saw some outdated content in some other places. Either way, we should not use that library anymore in favor of the official driver that comes in two flavors:<\/p>\n<p><strong>MongoDB Kotlin Driver (for applications using coroutines)<\/strong><\/p>\n<p>MongoDB Kotlin Sync Driver (for apps that require synchronous processing)<\/p>\n<h2 class=\"wp-block-heading\">Create &amp; Verify MongoDB Instance<\/h2>\n<p>If you already have a MongoDB instance installed on your machine, feel free to skip this step.<\/p>\n<p>On the other hand, if that is not the case, you can install it using the <a href=\"https:\/\/www.mongodb.com\/docs\/manual\/installation\/\">official manual<\/a>. Or, if you just like me have a Docker environment, you can run the following command:<\/p>\n<p>docker run &#8212;name my_awesome_mongo_name -d -p 27017:27017 mongo:8.0.4<\/p>\n<p>This way, we run the Mongo docker container and expose it\u2019s port 27017 .<\/p>\n<p>And to verify it running, we can run the docker ps :<\/p>\n<p>CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                      NAMES<br \/>\n9ecd8986ac34   mongo:8.0.4   &#171;docker-entrypoint.s\u2026&#187;   20 seconds ago   Up 19 seconds   0.0.0.0:27017-&gt;27017\/tcp   my_awesome_mongo_name<\/p>\n<p>As we can see, it was created successfully and we should be able to connect to it at localhost:27017<\/p>\n<p>We can assert that, as well, for example with a free and official tool- <a href=\"https:\/\/www.mongodb.com\/try\/download\/compass\">MongoDB Compass<\/a>:<\/p>\n<p>And if we are able to connect, it means that we can head to the next step. <\/p>\n<h2 class=\"wp-block-heading\">Create a New Ktor Project<\/h2>\n<p>Nextly, let\u2019s navigate to the <a href=\"https:\/\/start.ktor.io\/\" target=\"_blank\" rel=\"noopener\">Ktor Project Generator<\/a> page to create a fresh project from scratch. <\/p>\n<p>If you use the IntelliJ Ultimate Edition, then you can generate it in your IDE<\/p>\n<p>As we can see, we will be using <strong>Ktor 3.1.0<\/strong> with <strong>Netty<\/strong> and configuration in the <strong>YAML<\/strong> file.<\/p>\n<p>The <strong>important <\/strong>thing to mention here is that if we want to work with Kotlin coroutines, <strong>we should not select the below MongoDB plugin:<\/strong><\/p>\n<p>Because as we can see, this adds the MongoDB Kotlin Sync Driver for synchronous processing:<\/p>\n<p>So, in our case, to keep our Ktor application as vanilla as possible, we will not add any additional plugins. <\/p>\n<p>With that done, let\u2019s hit the <em>Download<\/em> button and let\u2019s import the project to our IDE.<\/p>\n<h2 class=\"wp-block-heading\">Add MongoDB Async Driver to Ktor<\/h2>\n<p>Before adding the necessary import, let\u2019s perform a small cleanup. <\/p>\n<p>Firstly, let\u2019s remove the Routing.kt file- we don\u2019t need it for this tutorial.<\/p>\n<p>Then, let\u2019s navigate to the Application.kt and let\u2019s get rid of routing config, as well. Eventually, we should have something like that: <\/p>\n<p> package com.codersee<\/p>\n<p>import io.ktor.server.application.*<\/p>\n<p>fun main(args: Array&lt;String&gt;) {<br \/>\n    io.ktor.server.netty.EngineMain.main(args)<br \/>\n}<\/p>\n<p>fun Application.module() {}<\/p>\n<p>With that done, let\u2019s add the MongoDB Kotlin Driver to work with coroutines. <\/p>\n<p>During the configuration, we decided to use the Gradle version catalog. So now, we must navigate to the libs.versions.toml inside the gradle directory and add the mongodb-version along with mongodb-driver-kotlin:<\/p>\n<p>[versions]<br \/>\nkotlin-version = &#171;2.1.10&#187;<br \/>\nktor-version = &#171;3.1.0&#187;<br \/>\nlogback-version = &#171;1.4.14&#187;<br \/>\nmongodb-version = &#171;5.3.1&#187;<\/p>\n<p>[libraries]<br \/>\nktor-server-core = { module = &#171;io.ktor:ktor-server-core-jvm&#187;, version.ref = &#171;ktor-version&#187; }<br \/>\nktor-server-netty = { module = &#171;io.ktor:ktor-server-netty&#187;, version.ref = &#171;ktor-version&#187; }<br \/>\nlogback-classic = { module = &#171;ch.qos.logback:logback-classic&#187;, version.ref = &#171;logback-version&#187; }<br \/>\nktor-server-config-yaml = { module = &#171;io.ktor:ktor-server-config-yaml&#187;, version.ref = &#171;ktor-version&#187; }<br \/>\nktor-server-test-host = { module = &#171;io.ktor:ktor-server-test-host&#187;, version.ref = &#171;ktor-version&#187; }<br \/>\nkotlin-test-junit = { module = &#171;org.jetbrains.kotlin:kotlin-test-junit&#187;, version.ref = &#171;kotlin-version&#187; }<br \/>\nmongodb-driver-kotlin = { module = &#171;org.mongodb:mongodb-driver-kotlin-coroutine&#187;, version.ref = &#171;mongodb-version&#187; }<\/p>\n<p>[plugins]<br \/>\nkotlin-jvm = { id = &#171;org.jetbrains.kotlin.jvm&#187;, version.ref = &#171;kotlin-version&#187; }<br \/>\nktor = { id = &#171;io.ktor.plugin&#187;, version.ref = &#171;ktor-version&#187; }<\/p>\n<p>With that done, let\u2019s navigate to build.gradle.kts and add the following line: <\/p>\n<p>implementation(libs.mongodb.driver.kotlin)<\/p>\n<p>Lastly, let\u2019s sync the gradle project so that the necessary libraries are fetched. <\/p>\n<h2 class=\"wp-block-heading\">Introduce Model Classes<\/h2>\n<p>Before we can start working with coroutines, we must prepare classes that will be translated into Mongo documents and vice versa. <\/p>\n<p>To do so, let\u2019s create the Product.kt class and put the following:<\/p>\n<p>import org.bson.codecs.pojo.annotations.BsonId<br \/>\nimport org.bson.types.ObjectId<\/p>\n<p>data class Product(<br \/>\n    @BsonId<br \/>\n    val id: ObjectId? = null,<br \/>\n    val name: String,<br \/>\n    val description: String,<br \/>\n    val price: Double,<br \/>\n    val category: ProductCategory,<br \/>\n    val tags: List&lt;ProductTag&gt;,<br \/>\n)<\/p>\n<p>enum class ProductCategory {<br \/>\n    VIDEO_GAMES, TOOLS, HOME_AND_KITCHEN, FOOD<br \/>\n}<\/p>\n<p>enum class ProductTag {<br \/>\n    EXCLUSIVE, HANDMADE, ORGANIC, BESTSELLER<br \/>\n}<\/p>\n<p>As we can see, all fields except the <em>id<\/em> field are plain Kotlin classes. And later, they will be serialized\/deserialized 1:1 when saving and retrieving from the database. <\/p>\n<p>When it comes to the id field, we mark it using the @BsonId annotation. Thanks to that, it will be serialized to the _id BSON field that represents a primary key of our document. Moreover, we assign a default null value to it. This way, the value will be generated automatically. <\/p>\n<h2 class=\"wp-block-heading\">Configure Ktor Connection to MongoDB<\/h2>\n<p>As the next step, let\u2019s connect our Ktor application to the Mongo instance. <\/p>\n<p>And for that purpose, let\u2019s navigate to Application.kt and implement the following logic: <\/p>\n<p>fun Application.module() {<br \/>\n    val settings = MongoClientSettings.builder()<br \/>\n        .applyConnectionString(<br \/>\n            ConnectionString(&#171;mongodb:\/\/localhost:27017&#187;)<br \/>\n        )<br \/>\n        .build()<\/p>\n<p>    val client = MongoClient.create(settings)<br \/>\n    val database = client.getDatabase(&#171;application&#187;)<br \/>\n    val productCollection = database.getCollection&lt;Product&gt;(&#171;products&#187;)<br \/>\n}<\/p>\n<p>Firstly, we instantiate the <em>MongoClientSettings<\/em> builder. A builder that allows us to configure the connection string for our database. Additionally, if you are looking for more configuration options, like reads or writes repetition, then you should start in there. <\/p>\n<p>Then, we create a new client and pass our settings to it. If you would like to, then you could use another variant of the create function that takes the connection String as an argument:<\/p>\n<p>public fun create(connectionString: String): MongoClient<\/p>\n<p>But, in my opinion, using the builder approach is a better choice in terms of extensibility. <\/p>\n<p>With that done, we get the instance of our database, by passing its name. Lastly, we obtain the MongoCollection that we will be injecting later into the product repository. Again, we pass the name of our collection, too.<\/p>\n<p>To verify, let\u2019s run our application.<\/p>\n<p>As a result, we should see the following text in the logs indicating that everything is perfectly fine: <\/p>\n<p>[cluster-ClusterId{value=&#8217;67bd5f4689251d25d5077fb7&#8242;, description=&#8217;null&#8217;}-localhost:27017] INFO  org.mongodb.driver.cluster &#8212; Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, cryptd=false, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=25, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=26289200, minRoundTripTimeNanos=0}<\/p>\n<h2 class=\"wp-block-heading\">MongoDB Coroutines CRUD Operations<\/h2>\n<p>After we did all of that preparation, we can finally create the ProductRepository class:<\/p>\n<p>class ProductRepository(<br \/>\n    private val productCollection: MongoCollection&lt;Product&gt;,<br \/>\n) { }<\/p>\n<p>And inject the collection in Application.kt : <\/p>\n<p>val productRepository = ProductRepository(productCollection)<\/p>\n<p>The constructor injection is a great way to make our code easier to test in the future. <\/p>\n<p>Anyway, coming back to the topic, let\u2019s learn how we can how we can perform basic CRUD operations with coroutines. <\/p>\n<h3 class=\"wp-block-heading\">Persits Products<\/h3>\n<p>Initially, our products collection is empty, so let\u2019s add the following code to start populating it: <\/p>\n<p>suspend fun save(product: Product): Product? {<br \/>\n    val result = productCollection.insertOne(product)<\/p>\n<p>    return result.insertedId<br \/>\n        ?.let { product.copy(id = it.asObjectId().value) }<br \/>\n}<\/p>\n<p>First of all, we must make our function <em>suspend<\/em>. Why? Because the <em>insertOne<\/em> we use is a suspend function, so we must invoke it either from the coroutine or another suspend function. <\/p>\n<p>When it comes to the persisting- the function that we use returns the InsertOneResult that allows us to either get a boolean informing if the write was acknowledged or the generated identifier of the saved product. And in my opinion, reading that and returning a Product instance with the updated field is a quite nice approach. <\/p>\n<p>After that, let\u2019s get back to the Application.kt and test this function: <\/p>\n<p>val saved = runBlocking {<br \/>\n    productRepository.save(<br \/>\n        Product(<br \/>\n            name = &#171;Product 1&#187;,<br \/>\n            description = &#171;Description 1&#187;,<br \/>\n            price = 19.99,<br \/>\n            category = ProductCategory.TOOLS,<br \/>\n            tags = listOf(ProductTag.EXCLUSIVE, ProductTag.BESTSELLER)<br \/>\n        )<br \/>\n    )<br \/>\n}<\/p>\n<p>println(saved)<\/p>\n<p>I know, the good, old println  <\/p>\n<p>Anyway, if we run our application, we should see the following in the logs: <\/p>\n<p>Product(id=67bd62f974edd96bbd59874a, name=Product 1, description=Description 1, price=19.99, category=TOOLS, tags=[EXCLUSIVE, BESTSELLER])<\/p>\n<p>Additionally, when we hit the <em>refresh<\/em> button in MongoDB Compass, we should see the following: <\/p>\n<p>And this proves that not only the Product was saved. But also, the application database and products collection was created by our client automatically. <\/p>\n<h2 class=\"wp-block-heading\">Find By ID<\/h2>\n<p> Nextly, let\u2019s implement the function to fetch product by identifier: <\/p>\n<p>suspend fun findById(id: String): Product? {<br \/>\n    val objectId = ObjectId(id)<\/p>\n<p>    return productCollection.find(<br \/>\n        eq(&#171;_id&#187;, objectId)<br \/>\n    ).firstOrNull()<br \/>\n}<\/p>\n<p>This time, we use the find method. And this function returns the <em>FindFlow<\/em> which is the <em>Flow <\/em>implementation for find operations. <\/p>\n<p>This function allows us to pass filters as arguments to it. And to get the particular product, we use the <strong>eq <\/strong>filter. One of the many filters that we can find in com.mongodb.client.model.Filters. We must remember that our identifier is of the <em>ObjectId<\/em> type, so we create a new instance from our String value. <\/p>\n<p>Lastly, we invoke the firstOrNull\u2013 the terminal operator that returns the first element emitted by the flow and then cancels flow\u2019s. We leverage the fact that only one element with such an identifier can be found in our database.<\/p>\n<p>So with all of that done, let\u2019s get back to the Application.kt and test this function: <\/p>\n<p>val found = runBlocking {<br \/>\n    productRepository.findById(&#171;67bd62f974edd96bbd59874a&#187;)<br \/>\n}<\/p>\n<p>val notFound = runBlocking {<br \/>\n    productRepository.findById(&#171;67bd62f974edd96bbd59874b&#187;)<br \/>\n}<\/p>\n<p>\/\/ Logs: <\/p>\n<p>\/\/ Product(id=67bd62f974edd96bbd59874a, name=Product 1, description=Description 1, price=19.99, category=TOOLS, tags=[EXCLUSIVE, BESTSELLER])<\/p>\n<p>\/\/ null<\/p>\n<p>As we can see, our test proves that <em>findById<\/em> not only works but also it simply returns null when nothing was found. No unexpected exceptions, etc. <\/p>\n<h3 class=\"wp-block-heading\">Updating Products<\/h3>\n<p>As the next step, let\u2019s add the function responsible for updating products: <\/p>\n<p>suspend fun update(id: String, product: Product): Product? {<br \/>\n    val objectId = ObjectId(id)<\/p>\n<p>    return productCollection.findOneAndReplace(<br \/>\n        filter = eq(&#171;_id&#187;, objectId),<br \/>\n        replacement = product,<br \/>\n        options = FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER)<br \/>\n    )<br \/>\n}<\/p>\n<p>Again, this function is a suspended function, and again we use the same combination of the <em>eq<\/em> filter and <em>ObjectId<\/em> to find the item we are interested in. <\/p>\n<p>The function that we use- findOneAndReplace\u2013 allows us to simply replace the existing document by passing a new version. Nevertheless, by default, this function returns the <strong>object before the update!<\/strong> And in my opinion, it makes more sense to return the updated versions in this case. And that\u2019s why we specify the additional option. <\/p>\n<p>As a note: if you would like to update just some fields, then the findOneAndUpdate may be a better choice. <\/p>\n<p>With all of that done, let\u2019s test our functionality: <\/p>\n<p>val updated = runBlocking {<br \/>\n    productRepository.update(<br \/>\n        id = &#171;67bd62f974edd96bbd59874a&#187;,<br \/>\n        product = Product(<br \/>\n            name = &#171;Updated Product 1&#187;,<br \/>\n            description = &#171;Updated Description 1&#187;,<br \/>\n            price = 20.11,<br \/>\n            category = ProductCategory.FOOD,<br \/>\n            tags = listOf(ProductTag.BESTSELLER)<br \/>\n        )<br \/>\n    )<br \/>\n}<\/p>\n<p>val notUpdated = runBlocking {<br \/>\n    productRepository.update(<br \/>\n        id = &#171;67bd62f974edd96bbd59874b&#187;,<br \/>\n        product = Product(<br \/>\n            name = &#171;Updated Product 2&#187;,<br \/>\n            description = &#171;Updated Description 3&#187;,<br \/>\n            price = 20.11,<br \/>\n            category = ProductCategory.FOOD,<br \/>\n            tags = listOf(ProductTag.BESTSELLER)<br \/>\n        )<br \/>\n    )<br \/>\n}<\/p>\n<p>\/\/ Logs: <\/p>\n<p>\/\/ Product(id=67bd62f974edd96bbd59874a, name=Updated Product 1, description=Updated Description 1, price=20.11, category=FOOD, tags=[BESTSELLER])<\/p>\n<p>\/\/ null<\/p>\n<p>As we can see, everything works as expected. Our function returns the <strong>updated<\/strong> product. And moreover, it does not throw any exceptions when a product is not found! <\/p>\n<h3 class=\"wp-block-heading\">Delete Products<\/h3>\n<p>Nextly, let\u2019s take a look at how we can remove products from our database: <\/p>\n<p>suspend fun deleteById(id: String): Boolean {<br \/>\n    val objectId = ObjectId(id)<\/p>\n<p>    val deleteResult = productCollection.deleteOne(<br \/>\n        eq(&#171;_id&#187;, objectId)<br \/>\n    )<\/p>\n<p>    return deleteResult.deletedCount == 1L<br \/>\n}<\/p>\n<p>At this point of our MongoDB coroutines tutorial, I think the code is quite descriptive. We use the same pattern we did previously to find by ID and we utilize the function from DeleteResult to get the count of deleted items. <\/p>\n<p>Of course, given we have only one item with a particular _id, the function returns <em>true<\/em> only if the count is equal to one. <\/p>\n<p>And again, a small note from my end if you would like to return the deleted product instead, then you can use the findOneAndDelete instead.<\/p>\n<p>I will skip the testing part here, you must trust me <\/p>\n<h3 class=\"wp-block-heading\">Case-insensitive Search In MongoDB<\/h3>\n<p>As the last step, I will show you how to utilize the function that we already know (find) to perform a case-insensitive search in MongoDB: <\/p>\n<p>fun find(<br \/>\n    title: String,<br \/>\n): Flow&lt;Product&gt; {<br \/>\n    return productCollection.find(<br \/>\n        regex(Product::name.name, title, &#171;i&#187;)<br \/>\n    )<br \/>\n}<\/p>\n<p>As we can see, this time we make use of the regex function, that will add the\u2026 regex filter  And thanks to the <strong>\u201ci\u201d<\/strong> option, the whole search will be case-insensitive.<\/p>\n<p>Additionally, we make use of the <em>name<\/em> field reference. We could use a simple String value- \u201cname\u201d- but thanks to our approach we won\u2019t need to remember to manually update the name in case of the update. <\/p>\n<p>Lastly, I just wanted to mention here that this is one of the approaches to tackle this issue. According to the Mongo docs, we have also two more options: <\/p>\n<p>1. Create a case-insensitive index with a collation strength of 1 or 2, and specify that your query uses the same collation.<br \/>2. Set the default collation strength of your collection to 1 or 2 when you create it, and do not specify a different collation in your queries and indexes.<\/p>\n<h3 class=\"wp-block-heading\">Adding Sorting and Pagination to Search Results<\/h3>\n<p>Lastly, let\u2019s make a small adjustment to add pagination and sorting to our logic.<\/p>\n<p>To do so, let\u2019s implement the Order Enum first:<\/p>\n<p>enum class Order {<br \/>\n    ASC, DESC<br \/>\n}<\/p>\n<p>And with that done, let\u2019s get back to our repository: <\/p>\n<p>fun find(<br \/>\n    title: String,<br \/>\n    sortBy: String,<br \/>\n    order: Order,<br \/>\n    limit: Int,<br \/>\n    skip: Int,<br \/>\n): Flow&lt;Product&gt; {<br \/>\n    val sort = when (order) {<br \/>\n        Order.ASC -&gt; ascending(sortBy)<br \/>\n        Order.DESC -&gt; descending(sortBy)<br \/>\n    }<\/p>\n<p>    return productCollection.find(<br \/>\n        regex(Product::name.name, title, &#171;i&#187;)<br \/>\n    )<br \/>\n        .sort(sort)<br \/>\n        .limit(limit)<br \/>\n        .skip(skip)<br \/>\n}<\/p>\n<p>As we can see, our function allows us to pass 4 more arguments during the invocation: <em>sortBy<\/em>, <em>order<\/em>, <em>limit<\/em>, and <em>skip<\/em>.<\/p>\n<p>So from now on, we can not only perform the search but also set the limit of results, ordering, as well as the order of items.<\/p>\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n<p>And that\u2019s all for this tutorial on how to work with MongoDB and Kotlin coroutines in Ktor. <\/p>\n<p>I hope you enjoyed it, and if you would like to learn more Ktor concepts in a fully hands-on manner, then check out my <a href=\"https:\/\/codersee.com\/courses\/ktor-server-pro\/\">course<\/a>. <\/p>\n<p>Lastly, if you would like to get the whole codebase, then you can find it in <a href=\"https:\/\/github.com\/codersee-blog\/ktor-mongodb-coroutines\" target=\"_blank\" rel=\"noopener\">this GitHub repository<\/a>.<\/p>\n\n<p>The post <a href=\"https:\/\/codersee.com\/mongodb-kotlin-coroutines-ktor\/\">MongoDB with Kotlin Coroutines in Ktor<\/a> appeared first on <a href=\"https:\/\/codersee.com\/\">Codersee &#8212; Kotlin on the backend<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>In this article, I will show you how to work with MongoDB and Kotlin coroutines in a Ktor application. Note: this article was created based on one of the services we implement in my Ktor Server Pro course. If you would like to learn how to expose a fully functional &#8230; <\/p>\n<div><a class=\"more-link bs-book_btn\" href=\"https:\/\/imcodinggenius.com\/?p=385\">Read More<\/a><\/div>\n","protected":false},"author":0,"featured_media":386,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-385","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-news"],"_links":{"self":[{"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=\/wp\/v2\/posts\/385"}],"collection":[{"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=385"}],"version-history":[{"count":0,"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=\/wp\/v2\/posts\/385\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=\/wp\/v2\/media\/386"}],"wp:attachment":[{"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=385"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=385"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/imcodinggenius.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=385"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}