Introduction to CouchDB with .NET part 4: continuing with the CouchDB HTTP API
May 22, 2017 1 Comment
Introduction
In the previous post we started looking into the CouchDB HTTP Web API. The API allows us to communicate with the CouchDB server using HTTP calls. This and the ubiquitous JSON arguments make integration with CouchDB quite straightforward for all platforms that are capable to execute HTTP calls and surely any serious programming language should be equipped with web request execution. The HTTP API responds with JSON by default and that also makes for a seamless integration.
We have primarily looked at various requests that did not modify the database or the documents. In this post we’ll continue with testing the database and document modification endpoints in the API.
Database creation and deletion
Database creation happens through simply calling the API with the database name as the resource. A PUT request will create a database if it doesn’t exist, e.g. PUT http://localhost:5984/products :
It responds with a 201 Created and a small JSON in the response body. The Location header indicates the resource URL for the database:
The location header is also returned when inserting a new document.
Let’s run the same request and see what happens:
As expected we cannot create another database with the same name.
Database deletion requires the same URL but the HTTP verb is DELETE:
We cannot supply just any database name. E.g. database names cannot include capital letters: http://localhost:5984/Cars
That’s quite a clear message:
Only lowercase characters (a-z), digits (0-9), and any of the characters _, $, (, ), +, -, and / are allowed. Must begin with a letter.
Run the PUT request again to recreate the products database before we continue.
Inserting a document in a database
Document insertion can happen in at least two ways. The first one is by way of a PUT request like above but we need to attach the document ID. Let’s try that first. Recall that the GET /_uuid endpoint returns a valid unique ID so I’ll use that to get one first: 3559d9c81c785b6bfc27a3490400be7d , but you can take one that your API instance returns. The PUT request must include a JSON body which represents the document. I’ll go with the following JSON:
{ "name": "desk", "category": "office", "dimensions": { "height": 100, "width": 150, "length": 150 }, "colour": "brown" }
The API responds with 201 Created and the following JSON in the response body format:
{ "ok": true, "id": "3559d9c81c785b6bfc27a3490400be7d", "rev": "1-dffc12f57f68be3da7f1deea7948bd48" }
The other way of inserting a document is the same as above but the HTTP verb changes to POST. Another change is that we don’t need to include the document ID anywhere in the request URL or the request body, it will be created automatically. Let’s test with POST http://localhost:5984/products and the following payload:
{ "name": "desk", "category": "office", "dimensions": { "height": 100, "width": 150, "length": 150 }, "colour": "brown" }
The request must also include the header Content-Type with the value application/json :
We can now execute the POST request:
We can still provide our own ID in the POST endpoint as well. We just need to include a JSON property called _id with the unique ID. I’ll go with the following JSON payload:
{ "_id": "3559d9c81c785b6bfc27a349040125c2", "name": "bed", "category": "home", "dimensions": { "height": 80, "width": 100, "length": 200 }, "colour": "white" }
The API will respond with the same ID:
{ "ok": true, "id": "3559d9c81c785b6bfc27a349040125c2", "rev": "1-6ff0fff1b611e6c6be83a83b9777a444" }
Updating an existing document
Updating a document involves both the ID and the revision number of the document that we want to update such as 1-6ff0fff1b611e6c6be83a83b9777a444 from the last example. We also need to supply the full document and not just the properties we want to update. In other words this is a document replacement. The revision number must be attached to the end of the PUT request URL with a ?rev query parameter. So taking this last example the update URL will be PUT http://localhost:5984/products/3559d9c81c785b6bfc27a349040125c2?rev=1-6ff0fff1b611e6c6be83a83b9777a444, i.e. the database name followed by the ID of the document we want to update. I want to change the colour of the product to green:
The API responds with a new revision number:
{ "ok": true, "id": "3559d9c81c785b6bfc27a349040125c2", "rev": "2-1e14bd9983c4e0726be869359110654c" }
We can also put the revision number in the JSON request as follows:
{ "_rev": "2-1e14bd9983c4e0726be869359110654c", "name": "bed", "category": "home", "dimensions": { "height": 80, "width": 100, "length": 200 }, "colour": "blue" }
…and we can omit the rev request parameter: PUT http://localhost:5984/products/3559d9c81c785b6bfc27a349040125c2. A third option is to add the revision number to the If-Match request header:
We can then omit the _rev property from the JSON payload:
{ "name": "bed", "category": "home", "dimensions": { "height": 80, "width": 100, "length": 200 }, "colour": "purple" }
So now we’re at revision 4:
{ "ok": true, "id": "3559d9c81c785b6bfc27a349040125c2", "rev": "4-b169b423cd8a96c458b8b51b2ccb1070" }
Deleting a document
To delete a document we have to send a DELETE request to the /dbname/documentID?rev=revisionID endpoint. E.g. to delete this last revision of the bed product the URL will be DELETE http://localhost:5984/products/3559d9c81c785b6bfc27a349040125c2?rev=4-b169b423cd8a96c458b8b51b2ccb1070 :
Alternatively we can send the revision number in the If-Match header like in the case of updates.
It’s interesting that we got a new revision number as a response. The reason is that this document hasn’t completely been deleted yet. Instead, it got a new JSON property called _deleted which was set to true. If we run a GET request to retrieve the document, i.e. against the URL http://localhost:5984/products/3559d9c81c785b6bfc27a349040125c2 we get the following JSON response:
{ "error": "not_found", "reason": "deleted" }
The deleted document will be kept until the database is compacted. DB compaction will also remove the document revisions except for the most recent one.
We can still get some basic information about the deleted document by running a GET request with the document and revision ID in the URL like http://localhost:5984/products/3559d9c81c785b6bfc27a349040125c2?rev=5-b1c8479dce3b432355cfd9df3a3fe546. The response will include the _deleted flag:
{ "_id": "3559d9c81c785b6bfc27a349040125c2", "_rev": "5-b1c8479dce3b432355cfd9df3a3fe546", "_deleted": true }
One way to undo a deletion is to first get the list of all available revisions via GET http://localhost:5984/products/3559d9c81c785b6bfc27a349040125c2?revs_info=true , i.e. /db-name/doc-id?revs_info=true. This will retrieve all the revisions of the provided document ID including the revision status:
{ "_id": "3559d9c81c785b6bfc27a349040125c2", "_rev": "5-b1c8479dce3b432355cfd9df3a3fe546", "_revs_info": [ { "rev": "5-b1c8479dce3b432355cfd9df3a3fe546", "status": "deleted" }, { "rev": "4-b169b423cd8a96c458b8b51b2ccb1070", "status": "available" }, { "rev": "3-d4d0109727801ca29810c5976a910767", "status": "available" }, { "rev": "2-1e14bd9983c4e0726be869359110654c", "status": "available" }, { "rev": "1-6ff0fff1b611e6c6be83a83b9777a444", "status": "available" } ] }
We can read the most recent revision that has NOT been deleted by analysing this document. It is 4-b169b423cd8a96c458b8b51b2ccb1070 in our case, i.e. we can read its properties by GET http://localhost:5984/products/3559d9c81c785b6bfc27a349040125c2?rev=4-b169b423cd8a96c458b8b51b2ccb1070. It returns the following document:
{ "_id": "3559d9c81c785b6bfc27a349040125c2", "_rev": "4-b169b423cd8a96c458b8b51b2ccb1070", "name": "bed", "category": "home", "dimensions": { "height": 80, "width": 100, "length": 200 }, "colour": "purple" }
We can then copy the JSON properties from here and insert a new document like we saw above in the document insertion demo: POST http://localhost:5984/products/ . Alternatively we can send a PUT request with the ID and revision number of the deleted document for an update operation.
We’ll continue in the next post with concurrency and data consistency.
You can view all posts related to data storage on this blog here.
Pingback: CouchDB Weekly News, May 25, 2017 – CouchDB Blog