There are many ways to use Couchbase in your web or mobile application through one of the various language SDKs. However, what if you were not using a backend language such as PHP or a mobile language such as Objective-C? What happens if you're using a statically hosted web service such as Amazon S3 where you only have HTML, CSS, and JavaScript abilities?

This is where PouchDB might come into play. PouchDB is a synchronization and storage library designed to work in a web browser using JavaScript. No backend code or SDKs are required to use this library with Couchbase. PouchDB can take the role of client side SDK and replicate the data to and from the Couchbase Sync Gateway. But PouchDB uses vanilla JavaScript, so what happens when your application uses AngularJS?

We're going to take a look at how to wrap various functions in PouchDB to make it more AngularJS friendly.

What We'll Need

There are a few requirements to the application we're going to build. We'll see how to obtain them along the way, but here is a taste so you know what you're getting yourself into.

  • Python for running a simple HTTP server or something similar
  • Couchbase Sync Gateway
  • PouchDB 4
  • AngularJS 1
  • The AngularJS UI-Router library version 0.2
  • Twitter Bootstrap 3

Laying The Foundation To Our Project

Before we get into the code, lets get our project structure situated and all libraries and styles into place.

Somewhere on your computer create the directory PouchDB and add the following directories at the root:

  • css
  • fonts
  • js
  • templates

At the root of your project you'll also want to create a file called index.html and a file called sync-gateway-config.json.

Now we need to download all the libraries for our project. Starting with Twitter Bootstrap, download the latest and place all the min.css files into the css directory of your project, all the min.js files into the js directory of your project, and all the font files in the fonts directory of your project.

With Twitter Bootstrap out of the way, next we need to download AngularJS and the AngularJS UI-Router. After downloading these libraries, place the min.js files into your project's js directory.

The last library to download is PouchDB. After downloading the min.js file, place it in the js directory of your project with all the rest.

Getting the Couchbase Sync Gateway

This project will require the Couchbase Sync Gateway in order to succeed. If you're unfamiliar, the Couchbase Sync Gateway is a middleman service that handles processing data between the local application (your AngularJS application) and the Couchbase Server. We won't be using Couchbase Server in this example so the Sync Gateway will act as our in-memory storage solution in the cloud.

The Couchbase Sync Gateway can be found via the Couchbase downloads section.

Building Our Project

Now that all the files are in place we can start coding our application.

Including All The Scripts And Styles

Let's start by including all styles and scripts into our index.html file. Open your index.html file and include the following code:

A few things to pay attention to in this index.html file. We've named our AngularJS application pouchapp and we are using a tag that might seem foreign:

That tag is part of the AngularJS UI-Router. This is a single page application where each screen is a partial that loads into that tag. It will make more sense soon.

Creating The AngularJS Logic File

Inside your project's js directory, create a file called app.js and include the following code:

A few things to note about this file in general and what we're going to do. The app.js file is where all our application logic will go. In it we have a .run() function that will be executed when the application launches, a .config() function that will configure all the screens in our application (UI-Router), and a .controller() function that will contain logic for the functionality of our particular application.

Also take note of $pouchDB as this is an AngularJS service that we'll design later.

The AngularJS Configuration

Since we're using the AngularJS UI-Router we need to configure our routes in the .config() function of the app.js file. The code would look like the following:

To sum it up, we have two routes (screens) here. We have a screen for showing a list of some sort and a screen that allows us to add or update items of the list. Both routes use an AngularJS controller called MainController, but each have a different template.

The AngularJS Route Templates

Each screen needs its own HTML to be shown to the user. In your project's templates directory, create list.html and item.html. Open the list.html and add the following code:

A lot of this might look crazy, but I'll explain it and it should make better sense. First off, we're creating a table and looping through an object creating a new table row for each iteration. Being that this is an object we're looping through, it will have both a key and a value which is why we have separated it. The value of this pair is also an object, which contains name information as well as email information.

The line with the edit and delete is taking information from the object to either delete it from storage or pass it to a different screen so we can edit it.

Now moving onto the second and last route for our application. Open the item.html file and include the following code:

Essentially this is just a form. We do have a save function in there that we'll look at in the AngularJS controllers section coming next.

The PouchDB AngularJS Service

Before we go any further we need to talk about the AngularJS service for PouchDB. Otherwise, everything else is not going to make sense. We're creating a service because we want things to be AngularJS friendly with PouchDB. We want to have reactive interfaces among other things. At the end of the app.js file, add the following code:

That is a lot to take in. We're essentially just wrapping many of the PouchDB functions. However, what matters the most here is the startListening and save functions.

Inside the startListening function we're listening for changes and broadcasting them through the application using $rootScope.$broadcast. Although you haven't seen it yet, AngularJS can pick up those broadcasts using $rootScope.$on. This makes changing the UI very easy.

In terms of the save function, we are checking to see if a document id was passed to us. If no document id was passed it means that this is a new document to be inserted, otherwise it is an update.

Getting Back Into Our Controller And Run Function

Lets start with the AngularJS .run() function because it is short. Add the following code:

Here we name the local database and we tell it that we want to sync to this remote location which is actually our Couchbase Sync Gateway. This sync happens continuously after we call it. By continuously I mean changes will be replicated between application and server for as long as the application is open.

Jumping into the controller code, add the following:

The application is already syncing, so we need to listen for any changes. Here is the $rootScope.$on I mentioned earlier. We have two of them because in one we are listening for changes (create or update) and in the other we are listening for deletes.

If the list.html page routed us here for an update, then we'll have a document id passed. In this scenario we can do a lookup and get the data for that particular document id to display in the form rather than leaving it blank which is the default.

Finally we have our save and delete functions.

The Sync Gateway Configuration

PouchDB and AngularJS is only half the story here. Sure they will create a nice locally running application, but we want things to sync. The Couchbase Sync Gateway is our endpoint for this and of course PouchDB works great with it.

Inside your project's sync-gateway-config.json file, add the following:

This is one of the most basic configurations around. A few things to note about it:

  • It uses walrus:data for storage which is in memory and does not persist. Not to be used for production.
  • All data is synced via the GUEST user, so there is no authentication happening here, but there could be
  • We are fixing CORS issues by allowing requests on localhost:9000

Testing The Application

At this point all our code is in place and our Sync Gateway is ready to be run. Start up the Sync Gateway by running the following in a command prompt or terminal:

Now this is where Python comes into play. You cannot just open the HTML files in a web browser. They must be served to prevent CORS issues. From the command prompt or terminal, with the project as your current working directory, run the following:

Visit http://localhost:9000 in your web browser and check it out!

Conclusion

You now saw how to create a web application that can sync with Couchbase using nothing more than AngularJS and PouchDB. No backend SDKs were required.

You can obtain the full working source code to this blog post via our Couchbase Labs GitHub repository.

Posted by Nic Raboy, Developer Advocate, Couchbase

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in Java, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Apache Cordova. Nic writes about his development experiences related to making web and mobile development easier to understand.

40 Comments

  1. Manvendra Prasad October 2, 2015 at 4:59 am

    Will N1QL support joining from 2 buckets on some common field? I have 2 buckets, Documents in both bucket has acct_id field. Can I join both bucket on acct_id to get result?

  2. Thanks for this great tutorial.
    Sir, i would like to use offline
    storage like PouchDB to store and retrieve images and i have been
    battling with it for quite some time now. I followed the home website of PouchDB but i wasn\’t able to save a single pic to it
    And i would be so grateful, if you can put me through with a simple illustration.
    Many thanks

    1. Why not convert your images to base64 strings and store the strings?

      1. Ok sir. Let me do that.
        Thank you.

      2. As advised, i use angular.tojson on the image data from the camera, and stored the result on pouchdb.
        However, i tried to retrieve the image and display but no success. The image is part of an object array with a reference name. Though, i use ng-repeat to loop through the object to extract the name and date of birth, but i don\’t know how to convert back the image to its original form in the expression field i.e {{}}
        Thank you sir.

        1. When you say camera, if you\’re talking about Ionic Framework, have you seen this?:

          https://blog.nraboy.com/2014/0

          More specifically there is this line in the example:

          To display a base64 image, you need to assign your HTML img tag \”data:image/jpeg;base64\” otherwise the tag won\’t understand the data you are passing:

          Hopefully that helps.

          Best,

  3. Nic, thanks for the blog tutorial. your controller coding style is different than what i learned and i was wondering if you would help in adding another page link to the app? I attempted to copy and duplicate your controller and that did not work. thank you

  4. Nic, having difficulties syncing with \”sync gateway\” back to my Couchbase Server. Do you have any tips? I do have an error that my \”cors\” is not turned on. Could that be why?

    1. Could be, but that is usually a browser based problem.

      Can you please provide more details in regards to your setup, and what is happening. The more information I have, the better I can help.

      Best,

      1. Nic, I mostly used the code per your instructions. My setup is developing a angular PouchDB to Couchbase app on my MS Surface Pro 3. this is my development device. My Couchbase server is on our Office server. Most of my development is when I am on same network as the CouchBase server and the main purpose is for my program to work in the field with limited connectivity with the office server.Please let me know if there is more you would like to know. Thanks

        1. Sorry for my late response, I was on vacation for the holidays.

          I\’m a bit confused by the information you gave me. Can you clear some things up?

          1. Is the web application (PouchDB, Angular, ect) running on your Surface Pro?
          2. Is Couchbase Sync Gateway running on your Surface Pro or on your office server?
          3. Is your Couchbase Server behind a firewall? In other words, regardless of where your Sync Gateway is hosted, does it have firewall permission to access Couchbase Server?

          Best,

          1. 1. the application will be running on our supervisors (30 of them) surface pro\’s, laptops.

            2. Couchbase and the sync gateway is on the server at the office.

            3. yes we have permissions to access the CouchBase server.

            i think i have it almost setup, i am getting this error? \”400 illegal database name: TCS-Mobile\”

            TCS-Mobile is the correct name of my bucket.

          2. Is this error via Sync Gateway or the console logs of the web application?

          3. console logs.

          4. Can I request that you follow my tutorial exactly? My guess is you\’ve got something custom to your setup that may not be correct.

            You might also look at this:



            Regards,

  5. Hi Nic,
    You seem to have a way of making the complex understandable.
    I am start-up “product owner” (scrum) and haven’t got my IT team in place yet. We will be building a mobile (not native) app and a web browser app to work with Couchbase server.

    “This is where PouchDB might come into play. PouchDB is a synchronization and storage library designed to work in a web browser using JavaScript.”

    Can Couchbase Lite / Mobile work in a web browser using JavaScript similar to what you have done using PouchDB?

    Thank you,

    Jim

    PS. I should have mentioned that I do not need offline capability for the web app.

  6. Great tutorial, I am in the middle of an offline first project at the moment so this is perfect. I am having a few issues with the CORS though. I ended up grabbing your code from Github to ensure I didn\’t have any silly typos but the issues still persist.

    I am using Chrome on a Mac.

    When I start the sync gateway I get the following:

    MacBook-Pro:~ leigh$ couchbase-sync-gateway/bin/sync_gateway Dropbox/wwwroot/offlinefirst/sync-gateway-config.json
    19:52:18.109745 Enabling logging: [CRUD+ REST+ Changes+ Attach+]
    19:52:18.109826 ==== Couchbase Sync Gateway/1.0.4(34;04138fd) ====
    19:52:18.109840 Configured Go to use all 8 CPUs; setenv GOMAXPROCS to override this
    19:52:18.109858 Opening db /test-database as bucket \”test-database\”, pool \”default\”, server <walrus:data>
    19:52:18.109893 Opening Walrus database test-database on <walrus:data>
    19:52:18.110497 Changes+: Notifying that \”test-database\” changed (keys=\”{_sync:user:}\”) count=2
    19:52:18.110505 Reset guest user to config
    19:52:18.110516 Starting admin server on 127.0.0.1:4985
    19:52:18.113169 Starting server on :4984 …
    2016/01/25 19:52:20 Walrus: Warning: Couldn\’t save walrus bucket: open data/walrustemp741838838: no such file or directory

    and in the browser I get this:

    XMLHttpRequest cannot load http://localhost:4984/test-database/?_nonce=1453751478847. A wildcard \’*\’ cannot be used in the \’Access-Control-Allow-Origin\’ header when the credentials flag is true. Origin \’http://localhost:9000\’ is therefore not allowed access.

    pouchdb-4.0.1.min.js:9 PouchDB error: the remote database does not seem to have CORS enabled. To fix this, please enable CORS: http://pouchdb.com/errors.html

    1. Interesting!

      Did you make sure to serve your AngularJS project rather than just opening the index file via the Finder? If you\’re serving your project and you\’re trying to use it via localhost:9000, can you tell me what version of Sync Gateway you\’re using?

      Best,

      1. Thanks for responding Nic,

        I tried with Sync Gateway 1.0.4 and 1.1 and also swapped between Pouch 4.0.1 and Pouch 5.2.0 without any luck. I definitely served the project using the Python server too. In the end I followed your YouTube tutorial (

        which had a very slight change in the sync-gateway-config.json file compared to this tutorial.

        When I used \’walrus:data\’ as per this tutorial I had the CORS issue but when I followed the Youtube one and used \”server\”:\”http://localhost:8091\” it was ok.

        At least I\’m up and running now!

        One last question, should this handle bi-directional sync as it is? I will have an Electron app, mobile app and web app all syncing to the Couchbase database so will need each PouchDB to push and pull

        1. The sync function in the $pouchDB service will handle bi-directional sync.

          In my video I used localhost:8091 because I had it connected to my live Couchbase Server instance. In this article I had Sync Gateway set up to use the development-only walrus temporary storage. Odd that the CORS issues went away when you switched to Couchbase Server, but I\’m happy that you got it working.

          If you haven\’t already, don\’t forget to do a search for the Electron tutorial I made that extends this post.

          Best,

  7. I keep getting a CORS error when trying to sync a PouchDB database. I\’ve tried the above sync-gateway-config.json and the version found in the Couchbase Sync Gateway docs here:
    http://developer.couchbase.com

    In both cases, I get the following error:

    XMLHttpRequest cannot load http://localhost:4985/test-database/.
    No \’Access-Control-Allow-Origin\’header is present on the requested
    resource. Origin \’http://localhost:3000\’ is therefore not
    allowed access.

    I posted this issue to StackOverflow, but no one has tried submitting an answer yet.
    http://stackoverflow.com/quest

    1. I assume you\’re using the following:

      Your error says you are trying to access via port 3000. Have you changed the port inside the CORS section of Sync Gateway from 9000 to 3000?

      Best,

      1. Thank you for getting back to me. I did. In my sync-gateway-config.json file, both \”Origin\” and \”Login Origin\” are set to port 3000. I\’m stuck at the moment and I\’m not sure what else to try. The question I posted to Stack Overflow has no answers yet either.

        1. I sent an email to our mobile team. They should follow up on this post soon. I personally don\’t see what can be wrong, but they may have a different perspective.

          Best,

        2. It looks like you\’re trying to hit the Sync Gateway admin port, which doesn\’t support CORS, as far as I know. Have you tried targeting the public port (4984)?

          1. That was the problem. I was using 4985 for some reason. Not sure why. Thank you for your help.

  8. Hi Nic, Great tutorial!!

    How would you integrate \”PouchDB\” with Spring Framework? So you can use Spring Security, other Java libs and expose a Restful API (maybe just a dummy wrapper on top of Couchbase). My main goal is to have a AngularJs client with offline autonomy, and auto-sync capabilities, but since I have a lot of modules written in Java and I need to integrate other libraries I would love to combine all this with a Spring Framework App in the backend.

    I have read also your tutorial http://blog.couchbase.com/deve… , but I don\’t know what is the best approach to integrate it with PouchDB.

    Any advice and suggestions will be greatly appreciated.

    Thanks,

    1. Hey @delkan@delkant:disqus,

      What if you used PouchDB as the model in your MVC setup where PouchDB and Sync Gateway are in charge of all data? Then you could keep your existing Java application for logic processing and rendering.

      If you needed further integration, the Couchbase Sync Gateway has a RESTful API that you can communicate with via your Java code. You can also manage custom authentication with Sync Gateway and your Java server.

      It might help me to get a more specific story on the things you\’re trying to do.

      Best,

      1. Actually, it is a good idea and works for me, but I rather prefer to use Spring security because I have all my users already on it. I have used in the past this http proxy( https://github.com/mitre/HTTP-… ) to secure solr\’s http requests with spring-security, I just run a test with the Sync Gateway as well, so only Spring-security authenticated users could access to the gateway. It worked great! Another thing I like here is that I can expose just at the bucket level (based on the url), all this using spring security and on the same port where my app is, just defining a new end point to serve cloudbase sync gateway.

  9. Hi Nic,

    Again nice tutorial. I have a question here.

    According to PouchDB documentation https://pouchdb.com/adapters.h
    Couchbase Sync Gateway **support is in progress**. It will work, but you may run into issues, especially with attachments.

    Because the support is in progress I am wondering if you can use PouchDB Server with Couchbase instead. I haven\’t find documentation about this but since both databases are implemented using a CouchDB-like protocol maybe they are compatible. Could please you confirm this?.

    Do you always need a Sync Gateway or something like PouchDB Server to access to a couchdb-like db from PouchDB?

    I am new on this, so I am sorry if my questions make no sense to you.

    Thank you,

  10. I\’m getting an error when I use the sample code. The controller sets up listeners each time the list view loads and it causes the error below:

    pouchdb.js:141 (node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit.

    Not quite an Angular ninja, but I would guess that that bit of code needs to be moved to a service.

  11. Hi Sir !!

    I wanted to develop the above application with CouchDB instead of Couchbase ? Is it the same like above ? which one is prefer, but mostly I am looking for CouchDB ? or do I need to do any modifications in the above code ? Can you provide any links or resources to develop with CouchDB ?

    Thanks in advance.

    1. You’ll have a much easier time with Couchbase. It is more feature rich and easier to use.

  12. Sir I have to develop a POS application that would be offline and sync data when user have access to internet. I want to know that How couchbase handles the conflicting states. e.g. Let there is sale happened on POS station 1 for item A. customer came with 2 quantities of Item A. At the same time station 1 was offline. and Local data show only 1 quantity remaining for Item A. But Actually sales manager had refilled the stock some time ago when station 1 was offline. it means that stock refill will not showed on station 1 until database go into sync. My question is How This scenario will be handled in offline / online db states?

    1. The revision history is maintained every time a document changes. So for example when you save your document the first time, it may look something like this:

      1-ad2348fd

      Where 1 is the revision and the rest is the hash associated with the save.

      If you end up with a document that shares the same id and same revision number, but different hashes, a conflict indicator will be provided. At this point, you can query the revision history for the document i.e. look at both revision entries of the same depth (or deeper) and apply your business logic to see which save should win.

      Should you run into the scenario where one device ends up with a higher revision history number, i.e the offline device saved 100 times, the higher revision save will win without conflict. However, you can always look through the revision history and apply further business logic.

      Does this answer your question?

      Best,

      1. Not completely. The scenario I provided , I ran into issue when offline station got sales order (2 items of Product A) which way more than what its local DB state (Only 1 Items remaining of product A). In this scenario how can sales transaction carried out while station (pos app) is in offline mode. The Only business solution i could think is to go into negative items for the time period when app is offline. because 1-2 = -1; and as soon Internet available I sync the state and manage the conflicts as you stated. can you present any better solution ?

        1. I am really not sure what you’re saying. Want to try again?

  13. HI Nic,

    Great tutorial. Very well explained.
    i wanted to point out, while injecting the AngularJs UI router, in app.js, inject [“ui.router”] instead of [“ui-router”] to avoid the injectormodulerr error.

    Thanks again

  14. HI Nic,
    MY apologies, i was the one who had mis-read. You had injected correctly.

Leave a reply