We just released Couchbase Mobile 1.1 and with this new version comes many new features. In this blog post, we’ll take a look at Sync Gateway webhooks and think of a few concrete examples for using them.
Webhooks
Prior to the 1.1 release, the only way to add custom logic in the form of plugins to Sync Gateway was to listen on the changes feed. While this approach still works, it isn’t the easiest to setup. With webhooks, you can simply provide a filter function and the URL to post the event to. Then, you handle the event with the back-end language of your choice (NodeJS, Java, Go, Ruby…).
Webhooks could be used for many different use cases:
- Messaging: targeted notifications to inform users of important events.
- Scheduling: kick-off large tasks that may be running on a web worker.
- Custom logic: extend the Sync Function capabilities by using a webhook to perform additional tasks on the document.
In the following section, you will focus on building the push notification feature for a news application to inform users when new articles that match their topics of interest are published. This tutorial is taken from the Couchbase by Example repository.
Architecture considerations
The Push Notification is a visual clue to let the user know that new content is available but it could also be for the application itself to pull the new articles from the server:
The reason we’re using the Web Push API instead of a continuous pull replication with Sync Gateway to check for new articles is that it can work even when the browser is closed. Compared to a web socket or long polling, which will only be kept alive as long as the browser and web page is kept open.
Scenarios
There are different scenarios for sending a push notification:
- Group Messaging: this concept was introduced in GCM to send notifications to up to 20 devices simultaneously. It’s very well suited for sending notifications to all devices that belong to a single user.
- Up and Down: a user updated a document and other users should be notified about it through a Push Notification.
Data Model
Let’s start with the smallest document, a Profile document holding registration tokens of the user’s devices and topics of interest:
1 2 3 4 5 6 7 8 9 10 |
{ "type": "profile", "name": "Oliver", "subscription": "free", // other values "expired", "premium" "topics": ["g20", "science", "nsa", "design"], "registration_ids": ["AP91DIwQ", "AP91W9kX"] } |
And the Article document may have the following properties:
1 2 3 4 5 6 7 8 9 |
{ "type": "article", "title": "Design tools for developers", "content": "...", "topic": "design" } |
Group Messaging
Imagine a scenario where a user is currently signed up on a freemium account and inputs a invite code to access the premium plan for a limited time. It would be nice to send a notification to all the user’s devices to fetch the additional content.
Brief: Send a one-off notification to freemium users that also have an invite code to unlock other devices.
Download the 1.1 release of Sync Gateway. You will find the Sync Gateway binary in the bin folder and examples of configuration files in the examples folder. Copy the exampleconfig.json file to the root of your project:
1 2 3 4 |
cp ~/Downloads/couchbase-sync-gateway/examples/exampleconfig.json /path/to/proj/sync-gateway-config.json |
Add three users in the configuration file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "log": ["CRUD", "HTTP+"], "databases": { "db": { "server": "walrus:", "users": { "zack": { "password": "letmein" }, "ali": { "password": "letmein" }, "adam": { "password": "letmein" }, "GUEST": {"disabled": true} } } } } |
Add a web hook with the following properties in the db object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
"event_handlers": { "document_changed": [ { "handler": "webhook", "url": "http://localhost:8000/invitecode", "filter": `function(doc) { if (doc.type == "profile" && doc.invite_code) { return true; } return false; }` } ] } |
Start Sync Gateway:
1 2 3 4 |
$ ~/Downloads/couchbase-sync-gateway/bin/sync_gateway ./sync-gateway-config.json |
Create a new file main.go to handle the webhook:
1 2 3 4 5 6 7 8 9 |
func main() { http.HandleFunc("/invitecode", func(w http.ResponseWriter, r *http.Request) { log.Println("ping") }) log.Fatal(http.ListenAndServe(":8000", nil)) } |
Start the Go server:
1 2 3 4 |
$ go run main.go |
Using curl, make a POST request to :4984/db/bulk_doc to save 3 Profile documents simultaneously:
1 2 3 4 5 6 |
curl -H 'Content-Type: application/json' -vX POST http://localhost:4985/db/_bulk_docs --data @profiles.json |
NOTE: To save space on the command line, the –data argument specifies that the request body is in profiles.json.
Notice that only Ali’s Profile document is POSTed to the webhook endpoint:
In the next section, you will configure a second web hook to notify all users when a new article that matches their interest is published.
Up and Down
Add another webhook entry that filters only documents of type article:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "handler": "webhook", "url": "http://localhost:8000/new_article", "filter": `function(doc) { if (doc.type == "article") { return true; } return false; }` } |
Add another handler in your Go server:
1 2 3 4 5 6 |
http.HandleFunc("/new_article", func(w http.ResponseWriter, r *http.Request) { log.Println("ping") }) |
Check that the webhook is working as expected by adding an Article document:
1 2 3 4 5 6 |
curl -H 'Content-Type: application/json' -vX POST http://localhost:4985/db/_bulk_docs --data @articles.json |
NOTE: The content of articles.json can be found here.
In this case, you have to do a bit more work to figure out what set of users to notify. This is a good use case for using a view to index the Profile documents and emitting the topic as the key and registrations IDs as the value for every topic in the topics array.
To register a view, we can use the Sync Gateway PUT /_design/ddocname endpoint with the view definition in the request body:
1 2 3 4 5 6 |
curl -H 'Content-Type: application/json' -vX PUT http://localhost:4985/db/_design/extras --data @view.json |
NOTE: The content of view.json can be found here.
Notice that the article we posted above has design in it’s topic and the only user subscribed to this topic is Adam. Consequently, if you query that view with the key “design”, only one (key, value) pair should return with the topic as key and device tokens as value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
curl -H 'Content-Type: application/json' -vX GET ':4985/db/_design/extras/_view/user_topics?key="design"' < HTTP/1.1 200 OK < Content-Length: 95 < Content-Type: application/json * Server Couchbase Sync Gateway/1.1.0 is not blacklisted < Server: Couchbase Sync Gateway/1.1.0 < Date: Wed, 17 Jun 2015 17:46:35 GMT < * Connection #0 to host left intact {"total_rows":1,"rows":[{"id":"4caa204e81b118cf23500f320e138aa8","key":"design","value":null}]} |
Now, you can edit the handler in main.go to subsequently query the user_topics view with the key being the topic of the article:
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 |
http.HandleFunc("/new_article", func(w http.ResponseWriter, r *http.Request) { log.Println("ping") var data map[string]interface{} body, _ := ioutil.ReadAll(r.Body) json.Unmarshal(body, &data) topic := data["topic"].(string) log.Printf("Querying user Profiles subscribed to %s", topic) var stringUrl string = fmt.Sprintf("http://localhost:4985/db/_design/extras/_view/user_topics?key="%s"", topic) res, err := http.Get(stringUrl) if err != nil { fmt.Print(err) return } if res != nil { var result map[string]interface{} body, _ = ioutil.ReadAll(res.Body) json.Unmarshal(body, &result) log.Printf("Result from the user_topics query %v", result["rows"].([]interface {})) } }) |
Run the bulk_doc request again and you will see the list of device tokens to use in the logs:
Conclusion
In this tutorial, you learned how to use Web Hooks in the scenario of GCM Push Notifications and used Couchbase Server Views to access additional information at Webhook Time™ :).
Good tutorial. Maybe it\’s time to update our GCM/APNS code
Hi,
Thanks for good documentation.
Is there any way to get the attachment over webhook or plugin implementation, as we are looking to create revision history of the attachment?
Thanks,
Satinder
Hi Thanks for the tutorial. I would like to know how to handle deletes. Since syncgateway only provides the id,rev & deleted attributes.
I have multiple doc types on a single bucket and I am interested in syncing(i.e. including deletes) only one particular type.
It looks like this should be done in the sync function http://developer.couchbase.com…. Feel free to ask any questions on the forums to go into your use case with a bit more detail https://forums.couchbase.com/c…
[…] Webhooks, a new integration mechanism, provides change notifications so you can easily integrate Couchbase Mobile with line of business apps, 3rd party services, etc. You can read more in the blog by Dev Advocate James Nocentini […]