Creating a simple Petfinder API client using Node.js

The following example shows how you can create a simple client for the Petfinder API using Node.js and the “http” module:

  1. Install the lodash module by typing the following command in the Terminal/command line:
    npm install lodash

  2. Create a new JavaScript file, app.js, in the same directory as you installed lodash and paste the following code:
    #!/usr/bin/env node
    
    var http = require("http");
    var url = require("url");
    
    var _ = require("lodash");
    
    var PETFINDER_KEY = "3a62ece31719a64dcf6726980917d7ad";
    
    /**
     * @constructor
     * Our simple petfinder class. For more information on the Petfinder API and all
     * of its options, see http://www.petfinder.com/developers/api-docs
     *
     * The following usage restrictions apply to users of the API:
     * - Total requests per day: 10,000
     * - Records per request: 1,000
     * - Maximum records per search: 2,000
     * 
     * @param  {String} key Your Petfinder API key. For more information, see
     * http://www.petfinder.com/developers/api-key
     * @return {Object} A petfinder instance.
     */
    function petfinder(key) {
        var self = this;
        self.KEY = key;
    
        /**
         * @private
         * Internal method which merges the supplied object with a default set of
         * HTTP request parameters for the Petfinder API.
         * 
         * @param {Object} opts The object to merge into our default set of HTTP 
         * request variables.
         * @return {Object} The merged default and supplied objects.
         */
        self._httpOptions = function (opts) {
            var defaultOpts = {
                "protocol": "http:",
                "host": "api.petfinder.com",
                "query": {
                    "format": "json",
                    "key": self.KEY
                }
            };
            // Seems underscore.js and lodash don't really handle nested object
            // merging too well, so lets merge the nested query objects ourselves.
            _.defaults(defaultOpts.query, opts.query);
            _.defaults(defaultOpts, opts);
            return defaultOpts;
        };
    
        /**
         * @private
         * Internal method which creates an HTTP GET request from the supplied URL
         * object and calls the specified callback function with the chunked data as
         * a JSON object.
         * 
         * @param {Object} opts The URL object to pass to the `http.get()` 
         * method.
         * @param {Function} callback The callback function to call with the JSON
         * output from the HTTP request.
         */
        self._httpGet = function (opts, callback) {
            var uri = url.format(opts);
            http.get(uri, function (res) {
                var data = "";
                res.on("data", function (chunk) {
                    // Save the chunked data (as a string) to the data variable.
                    data += chunk.toString();
                });
                res.on("end", function () {
                    // Convert the data string to a JSON object and pass it to the
                    // specified callback function.
                    callback(JSON.parse(data));
                    // console.log(uri);
                });
            });
        }
    
        self.breed = {
            /**
             * The "/breed.list" route. This route calls the Petfinder "/breed.list"
             * API to query for a list of dogs.
             *
             * @param {Function} callback The callback function to call once the
             * list of animals has been received from the Petfinder API.
             */
            list: function (callback) {
                var options = self._httpOptions({
                    "pathname": "/breed.list",
                    "query": {
                        "animal": "dog"
                    }
                });
    
                // Invoke the HTTP GET request and call the specified callback when
                // finished.
                self._httpGet(options, callback);
            }
        };
    
        self.pet = {
            /**
             * The "/pet.find" route. This route calls the Petfinder "/pet.find" API
             * to find dogs for the specified zip-code.
             * 
             * @param {Number} location The zip-code to search.
             * @param {Object} opts An object containing a bunch of optional
             * arguments to send to the "/pet.find" request.
             * @param {Function} callback The callback function to call after we
             * get a response from the HTTP GET request.
             */
            find: function (location, opts, callback) {
                var options = self._httpOptions({
                    "pathname": "/pet.find",
                    "query": {
                        "animal": "dog",
                        "location": location
                    }
                });
    
                opts = opts || {};
    
                // Merge the optional `opts` in to the `options.query` object.
                _.extend(options.query, opts);
    
                // Invoke the HTTP GET request and call the specified callback when
                // finished.
                self._httpGet(options, callback);
            },
    
            /**
             * The "/pet.get" route. This route calls the Petfinder "/pet.get" API 
             * to get the pet with a specified `id`.
             * 
             * @param {Number} id The id of the animal that we want to get info for.
             * @param {Function} callback The callback function to call after we
             * get a response from the HTTP GET request. 
             */
            get: function (id, callback) {
                var options = self._httpOptions({
                    "pathname": "/pet.get",
                    "query": {
                        "id": id
                    }
                });
    
                // Invoke the HTTP GET request and call the specified callback when
                // finished.
                self._httpGet(options, callback);
            },
    
            /**
             * The "/pet.getRandom" route. This route calls the Petfinder 
             * "/pet.getRandom" API to get a random dog and return its basic info.
             *
             * @param {Function} callback The callback function to call after we
             * get a response from the HTTP GET request.
             */
            getRandom: function (callback) {
                var options = self._httpOptions({
                    "pathname": "/pet.getRandom",
                    "query": {
                        "animal": "dog",
                        "output": "basic"
                    }
                });
    
                // Invoke the HTTP GET request and call the specified callback when
                // finished.
                self._httpGet(options, callback);
            }
        };
    };
    
    // Create a new `petfinder` object using our API key.
    var pf = new petfinder(PETFINDER_KEY);
    
    // Find 10 senior dogs in the 94089 (Sunnyvale, CA) area.
    // http://api.petfinder.com/pet.find?format=json&key=3a62ece31719a64dcf6726980917d7ad&animal=dog&location=94089&age=senior&count=10
    pf.pet.find(94089, {"age":"senior", "count":10}, function (data) {
        console.log("get by location:");
        // Loop over the array of pets and display their id/name/sex/age.
        data.petfinder.pets.pet.forEach(nameSexAge);
    });
    
    // Get a pet by its numeric id.
    // http://api.petfinder.com/pet.get?format=json&key=3a62ece31719a64dcf6726980917d7ad&id=24395698
    pf.pet.get(24395698, function (data) {
        console.log("get by id:");
        nameSexAge(data.petfinder.pet);
    });
    
    // Get a random dog.
    // http://api.petfinder.com/pet.getRandom?format=json&key=3a62ece31719a64dcf6726980917d7ad&animal=dog&output=basic
    pf.pet.getRandom(function (data) {
        console.log("get random dog:");
        nameSexAge(data.petfinder.pet);
    });
    
    // Get a list of dog breeds.
    // http://api.petfinder.com/breed.list?format=json&key=3a62ece31719a64dcf6726980917d7ad&animal=dog
    pf.breed.list(function (data) {
        var breeds = data.petfinder.breeds.breed;
        // Iterate over the array of breeds and remove that nested `$t` variable.
        breeds = breeds.map(function (breed) {
            return breed.$t;
        });
        console.log("get list of breeds:");
        console.log("\t", breeds.slice(0, 5).join(", "));
    });
    
    function nameSexAge(data) {
        console.log("\t[%d] %s/%s/%s", data.id.$t, data.name.$t, data.sex.$t, data.age.$t);
    }
    
  3. Run your JavaScript app by typing the following in the Terminal:
    node app.js

    Your console should now show the results of your HTTP requests. Since the http.get() method is asynchronous, the requests and responses may come slightly out of order.

First, we’re creating a new function which will act as our petfinder class. Next we save our Petfinder developer KEY to our petfinder class instance, and then define two helper methods: _httpOptions() and _httpGet().

The _httpOptions() method takes a single parameter, opts, and merges it with a default set of options (protocol, host, query string parameters) which will be passed to our HTTP GET request.

The _httpGet() method takes two parameters: opts and callback. The opts parameter is our merged options from the _httpOptions() method, which will be converted to a query string and passed to the http.get() method. Once we get all of our data from the http.get() method, we convert the returned data to a JSON object and pass it to the specified callback function.

Next, we define our class methods. First we define a breed object and define a list function. This will allow our client to invoke the function by calling something something like mypetfinder.breed.list(...). The breed.list() method begins by declaring a options variable which will merge our parameters with the default parameters defined in the _httpOptions() method. Finally the breed.list() method invokes our _httpGet() method and passes our options object and the specified callback function.

self.breed = {
    list: function (callback) {
        var options = self._httpOptions({
            "pathname": "/breed.list",
            "query": {
                "animal": "dog"
            }
        });

        self._httpGet(options, callback);
    }
};

The rest of the petfinder class defines the following methods: pet.find(), pet.get(), and pet.getRandom(). Each of these methods follows the same general flow as the breed.list() method above.

Finally, we create a new petfinder class instance (and pass our Petfinder API key) and name the variable pf. Next, we test each of our petfinder class methods. The following snippet shows how we create our petfinder class instance and call the pet.get() method, passing the animal’s Petfinder id and specifying a callback function. Once the internal _httpGet() method gets a response from the http.get() method, the specified callback function is called and passed a JSON object.

// Create a new `petfinder` object using our API key.
var pf = new petfinder(PETFINDER_KEY);

// Get a pet by its numeric id.
// http://api.petfinder.com/pet.get?format=json&key=3a62ece31719a64dcf6726980917d7ad&id=24395698
pf.pet.get(24395698, function (data) {
    console.log("get by id:");
    nameSexAge(data.petfinder.pet);
});

Note: Since the http.get() method is asynchronous, the petfinder class APIs may not be returned in the same order in which they were called.

Leave a Reply

Your email address will not be published.