When building applications with Couchbase Mobile there are a lot of interesting possibilities for server-side integration.
Creating business logic to resolve synchronization conflicts is one common use. The Sync Gateway document “changes” feed makes all sorts of event-driven operations easy to implement, too. I’ll explore this in upcoming posts.
An app can use both Sync Gateway and Couchbase Lite by directly calling the ReST API for each. That requires writing a bunch of boilerplate code that isn’t much fun, though. PouchDB offers a compatible JavaScript option, but now there’s a lighter-weight alternative.
Not too long ago Couchbase switched to using Swagger to document the Couchbase Mobile APIs. Swagger does far more than create
documentation, though. With a single Swagger spec, you can generate documentation, server stub code, test cases, a sandbox site, and more. Best of all for me, Swagger can generate client SDKs… in 40 different languages!
In this post, I’m going to show how to use the Swagger Node.js client. We’ll build a super simple app that monitors changes in documents in Couchbase. This will become the basis for building some other cool stuff coming up. Let’s dive in.
What we’ll need
To follow along, you’ll want
- Node.js (I’m using v6.9.1 on a Mac)
- Swagger JS (The Swagger Node.js client)
- Couchbase Sync Gateway
- The Sync Gateway Swagger spec
I’m assuming you already have Node.js installed.
Install the Swagger JS Library
From a command line shell, change to the directory where you will keep your project.
To get the Swagger client and dependencies, run
1 |
$ npm install swagger-client |
(You will see some warnings, which you can ignore.)
Install Sync Gateway
Download the Sync Gateway package for your platform here. (If your are running Linux and don’t see a package for your distribution, see the instructions in
this blog post for help.)
In order to run Sync Gateway, we need to create a configuration file. Here’s a simple setup we can use. Copy the text here and paste it into a file called “sync-gateway-config.json”, or something similar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "log": ["HTTP+"], "adminInterface": "127.0.0.1:4985", "interface": "127.0.0.1:4984", "CORS": { "origin":["*"], "loginorigin":["*"], "headers":["Content-Type"], "maxAge": 1728000 }, "databases": { "test": { "server": "walrus:", "users": { "GUEST": {"disabled": false, "admin_channels": ["*"] } } } } } |
This configuration uses a built-in database made for testing Sync Gateway. The server will only respond to connections on localhost, too. You can find resources to learn more about configuring Sync Gateway at the end of this post.
Start Sync Gateway by running
1 |
$ /path/to/sync_gateway sync-gateway-config.json |
You’ll see some logging output. You might find it interesting to watch the logs when running the app later.
Make a copy of the Sync Gateway Swagger spec
You can find the Swagger spec for the public Sync Gateway ReST APi (versus the admin API) at https://docs.couchbase.com/sync-gateway/current/_attachments/sync-gateway-admin.yaml.
Swagger specs are stored as JSON. Although Swagger can download the text directly, it’s best to make a copy. That way if the spec changes it won’t break your app.
Copy the information into a file in your projet directory called sync-gateway-spec.json, or something similar. The file needs a .json extension for Node to process it.
Create the Node.js app
Let’s put the app together. Open up a new file in your project directory that ends with a .js extension. I used a file named app.js.
1 2 |
const Swagger = require('swagger-client'); const spec = require('./sync-gateway-spec.json'); |
The first line imports the Swagger client and makes it available. The second line is a bit more unusual. It initializes the spec
object from the JSON in the file. This is why the .json extension is important. It’s how Node recognizes we’re
importing JSON, not a regular package.
The spec file has a host hardwired in, but let’s go ahead and set it ourselves.
1 2 3 4 |
const SYNC_GATEWAY_HOST = 'localhost:4984'; spec.host = SYNC_GATEWAY_HOST; |
Next, we’ll prepare the parameters for our call to the _changes
endpoint.
1 2 3 4 5 6 7 8 9 |
let query = { db: 'test', filter: 'sync_gateway/bychannel', channels: 'notification', active_only: true, include_docs: true, feed: 'longpoll', timeout: 0 }; |
Notice we have a mix here of the database name (test), which ends up being part of the endpoint URL, and some options we want to set.
Now that we’ve initialized what we need, we can create the Swagger client instance.
1 2 3 4 5 6 7 8 9 10 |
let client = new Swagger({ spec: spec, success: function() { monitor(client); }, function(error) { console.log('client error: ', error.statusText); process.exit(1); } }); |
There are several important things to note here.
We’re using the client syncronously. If you want, you can use JavaScript Promises by specifying usePromise: true
. I didn’t want to do that since I’m not doing any real processing and it complicates the code a bit.
Instead of spec: spec
, we could have listed a url for the Swagger specification using url:
. Again, you should avoid this, at least in production, because the spec could change remotely and break your code.
The success:
entry specifies two functions. Both are callbacks. The first is called if construction succeeds, the second if it fails. These are “global”, in a sense. These functions act as defaults if we don’t specify different ones to the
individual calls. That’s really handy in the error case.
We assign the instance to a variable client
. The client instance is filled out asynchronously. It isn’t ready to use until the success callback happens. So we have the interesting construct that client
is used internally in the
callback. In the Promises version, client would be passed as the argument in the .then
clause.
In the next part of the code, we use the client to listen to the _changes
endpoint.
1 2 3 |
function monitor(client) { client.database.get_db_changes(query, message); } |
This presents a problem: how do we find out the name of the function we want to call? There’s no fixed mapping between endpoints and functions names. This is another handy feature of Swagger. The client class is self-documenting. You call help()
at whatever level you need. For example, to find out about endpoints under database, add this in your code:
1 |
client.database.help(); |
The help output shows up in the command line output (or your IDE’s console). It will describe the endpoints and all the possible parameters.
Going back to the client call, we see that we passed both the database name (required), and several parameters in lumped together in the query
object. This was a little confusing. You might expect these to be separated out, but that won’t
work. None of the examples I found explained this.
We supplied a callback function message
. (Notice that’s used if the ReST call succeeds. We rely on the default to handle failures.)
1 2 3 4 5 |
function message(response) { console.log(response.data); query.since = response.obj.last_seq; monitor(client); } |
The callback receives an object containing all the details of the response. I just wrote this app to show the loop structure needed to listen to the Sync Gateway changes feed. Hence, the message
function doesn’t do much. We write the call
response out for debugging purposes. We then want to go back and listen for the next update. Sync Gateway knows what to push based on sequence IDs. To control that, we use the last_seq
information from our last response.
That’s it. Here’s the complete code for the app.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
const Swagger = require('swagger-client'); const spec = require('./sync-gateway-spec.json'); const SYNC_GATEWAY_HOST = 'localhost:4984'; spec.host = SYNC_GATEWAY_HOST; let query = { db: 'test', filter: 'sync_gateway/bychannel', channels: 'notification', active_only: true, include_docs: true, feed: 'longpoll', timeout: 0 }; let client = new Swagger({ spec: spec, success: function() { monitor(client); }, function(error) { console.log('client error: ', error.statusText); process.exit(1); } }); function monitor(client) { client.database.get_db_changes(query, message); } function message(response) { console.log(response.data); query.since = response.obj.last_seq; monitor(client); } |
Final notes
You can fire up your app and try it out. Make sure Sync Gateway is running. Then run
1 |
$ node app.js |
You should see a response in the Sync Gateway logs and something like this output by the app itself:
1 2 3 4 |
{"results":[ {"seq":1,"id":"_user/GUEST","changes":[]} ], "last_seq":"1"} |
Now, add a new document. You can do this with cURL on the command line. Here’s an example.
1 |
$ curl -X PUT -H "Content-Type: application/json" -d '{ "key": "value", "channels": "notification" }' http://localhost:4984/test/doc |
Your Node app should output something like this:
1 2 3 4 |
{"results":[ {"seq":2,"id":"doc","doc":{"_id":"doc","_rev":"1-d0efa985de274451fc7cdcf471152ce2","channels":"notification","key":"value"},"changes":[{"rev":"1-d0efa985de274451fc7cdcf471152ce2"}]} ], "last_seq":"2"} |
Other Resources
Read more about Couchbase Mobile.
Read about the Sync Gateway ReST APIs.
The Swagger JS client GitHub respository has some useful documentation included.
Find out about Swagger itself at http://swagger.io.
The online Swagger editor is really useful. You can generate client SDKs here, or even try out the API directly. Just paste the Swagger spec into the editor. http://editor.swagger.io/
There’s a React Native Couchbase community project that uses the JavaScript client. You can find some interesting examples there at https://github.com/couchbaselabs/react-native-couchbase-lite
Postscript
Check out more resources on our developer portal and follow us on Twitter @CouchbaseDev.
You can post questions on our forums. And we actively participate on Stack Overflow.
You can follow me personally at @HodGreeley
Somewhat ironically, the link to the Swagger sync-gateway spec.json is invalid at the moment. The code here is very useful and interesting. Do you have an updated link to that spec file?
It looks like accessible at http://docs.couchbasemobile.com/sync-gateway-public/spec.json
Also docs are rendering the testing tool correctly at
https://developer.couchbase.com/documentation/mobile/current/references/couchbase-lite/rest-api/index.html
Maybe it was temporary issue
I think I just shot off enough flares and someone rescued me. :P Thanks!
Another question, this time about the
usePromise
option in Swagger. How do I keep the monitoring ongoing while using Promises (just as in your non-promise example)?I rewrote the methods to use Promises, but I get one pass at log output containing the sequence of changes and the software exits, rather than stay alive. Any idea on how to configure it?
client = new Swagger({options})
.then(monitor)
.then(message)
.catch(error)
Forgive me for practicing in my little echo chamber. :-P
If anyone out there wants to use promises, the pattern is to return a new Swagger client every time a message is logged. The promise on the initial client will be fulfilled. The new client will need to be created, which will monitor after it’s done pending. I can’t post code here, but it works charmingly.
You essentially have to build a loop where Promise resolution passes on to a next stage. I’ll be writing up a full example, but here’s a snippet to give an idea.
new Swagger({
spec: spec,
usePromise: true
})
.catch(error => die('Unable to create client instance:', error))
.then(result => main(result))
.catch(error => die('Failure in main:', error));
function main(client) {
// Some init code
Changes.monitor(client);
}
class Changes {
static init(client) {
Changes.query = {
db: db.db,
active_only: false,
include_docs: true,
feed: 'longpoll',
timeout: 0
};
}
static monitor(client) {
client.database.get_db_changes(Changes.query)
.then(response => Changes.parse(client, response))
.catch(error => warn("error receiving changes:", error));
}
static parse(client, response) {
for (const record of response.obj.results) {
// App logic
Changes.query.since = record.seq;
}
Changes.monitor(client);
}
}
I’m willing to compare notes w/ you over email, Hod. For legal reasons, I can’t post code here, but via work email I can (b/c we have an NDA w/ CB on file). I managed to get it to work in 30 lines of ES6.
Cool, yes, I’d like to see how you’re approaching it. I think you have my email, but it’s just hod
at
…[…] Once you have the API spec defined for you endpoints, you get several valuable capabilities. My two favorite are the “live” embeddable documentation and the client libraries. Take a look at this short video for a demonstration of some of the features of Swagger. For an example of using a Swagger JavaScript client, take a look at this blog post: https://www.couchbase.com/node-js-swagger-monitor-document-changes-couchbase-mobile/ […]
Thanks for the article, it’s pretty neat but to be honest I’m a bit confused. I think the statement… “PouchDB offers a compatible JavaScript option, but now there’s a lighter-weight alternative” is the cause of my confusion. This doesn’t seem to me like an alternative to using an offline database like PouchDB that syncs with couchbase.
I meant compatible in the sense of acting as a higher level client for the Sync Gateway ReST API. Read it as “both PouchDB and Swagger + the right spec give you Sync Gateway-compatible ReST clients”.
I’m talking about server-side Node here, so implicitly assuming that you’re only off the network when something is seriously wrong. Hence, local storage isn’t needed. Not to dismiss the interesting offline scenarios, just to say that’s not what I’m addressing here.