Creating a Node module for the Petfinder API

In a previous example, “Creating a simple Petfinder API client using Node.js”, we saw how you could create a simple client for the Petfinder API using Node.js and the “http” module.

The following example shows how you can convert the petfinder function to a reusable Node.js module.

  1. Create a new folder, petfinder-test, for our Node application.
  2. In the petfinder-test/ folder, create a new subfolder named node_modules.
  3. In the petfinder-test/node_modules/ folder, create a new subfolder named petfinder.
  4. Using your Terminal/command line, type the following command in the newly created petfinder-test/node_modules/petfinder/ folder: npm init

    This will walk you through a brief text-based wizard for creating a package.json file for your new project. You can accept most of the default values or enter your own values, but here is what my package.json file looks like:

    {
      "name": "petfinder",
      "version": "0.0.1",
      "description": "Node.js library for the Petfinder API",
      "main": "petfinder.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "repository": "",
      "keywords": [
        "Petfinder"
      ],
      "author": "Peter deHaan",
      "license": "BSD"
    }
    
  5. Install the lodash module by typing the following command in your Terminal: npm install lodash --save
    This will install the lodash module and save it as a dependency to our package.json file.

  6. Create a new JavaScript file, petfinder.js, in the node_modules/petfinder/ folder and paste the following content:
    var http = require("http");
    var url = require("url");
    
    var _ = require("lodash");
    
    exports.petfinder = petfinder;
    
    /**
     * @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/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} options 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 (options, callback) {
            var uri = url.format(options);
            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));
                });
            });
        };
    
        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 = {
            // "/pet.find"
            /**
             * 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);
            }
        };
    }
    
  7. Create a /petfinder-test/app.js file and paste the following content:
    var petfinder = require("petfinder").petfinder;
    
    // Create a new `petfinder` object using our API key.
    var pf = new petfinder("3a62ece31719a64dcf6726980917d7ad");
    
    // 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);
    }
    

Now you can run your Node application by typing the following in the Terminal:

node app.js

If everything worked, you should see the results returned from the Petfinder API in your Terminal.

Leave a Reply

Your email address will not be published.