Increment firebase value from javascript, subject to constraint

See the reference documentation for a transaction:

var ref = firebase.database().ref('node/clicks');
ref.transaction(function(currentClicks) {
  // If node/clicks has never been set, currentRank will be `null`.
  return (currentClicks || 0) + 1;
});

The above will simply increment the value atomically, without having he option of users overwriting each other's results.

Next up, make sure that the values can never be > 20:

var ref = firebase.database().ref('node/clicks');
ref.transaction(function(currentClicks) {
  // If node/clicks has never been set, currentRank will be `null`.
  var newValue = (currentClicks || 0) + 1;
  if (newValue > 20) {
    return; // abort the transaction
  }
  return newValue;
});

For good measure you'll also want to set up your security rules to only allow clicks up to 20. Security rules are enforced on the Firebase Database server, so this ensures that even malicious users cannot bypass your rules. Based on the examples in the Firebase documentation on validating data:

{
  "rules": {
    "node": {
      "clicks": {
        ".validate": "newData.isNumber() && 
                      newData.val() >= 0 && 
                      newData.val() <= 20"
      }
    }
  }
}

There's a new method ServerValue.increment()in firebase JavaScript SDK v7.14.0

It's better for performance and cheaper since no round trip is required.

See here

Added ServerValue.increment() to support atomic field value increments without transactions.

API Docs here

Usage example:

firebase.database()
    .ref('node')
    .child('clicks')
    .set(firebase.database.ServerValue.increment(1))

Or you can decrement, just put -1 as function arg like so:

firebase.database()
    .ref('node')
    .child('clicks')
    .set(firebase.database.ServerValue.increment(-1))