At the beginning of each fiscal year, Cisco's global sales organization brings together the vast majority of its sales force and sales engineering teams.  This adds up to roughly 20,000 people from around the globe.  Of course, we had the DevNet Zone as part of this event to help our sales engineers and technical sales people understand Cisco's APIs and developer capabilities.

 

The DevNet team wanted to have fun and immersive experiences for the SEs, and we came up with the DevNet API Scavenger Hunt!  This turned out to be a really fun (and educational) activity for everyone involved.  The way it worked was that each day, we would release three challenges to the participants, and they would have about 24 hours to work on those challenges.  The following day, when we released the new questions, we also posted the answers to the previous day's challenges.

 

Of course, to be consistent with the theme, I created an API for the participants to use for answer submission!

 

To summarize my requirements, I needed the following:

 

  • Build an API quickly (~a couple days)
  • Data Persistence
  • User and API Authorization
  • Only an API (No UI for user)
  • Package in a “cloud native” way

 

 

Building an API Quickly

 

I had worked with Swagger in the past, but with the most recent releases of Swagger 2.0 and corresponding tooling, I wanted to give it a new pass.  The swagger-node package was new to me, and so I thought I'd investigate.  Because I was starting a new app, I could rely on the "document-first" method of API creation.  If you're not familiar with this concept, the idea is to create your API specification using a description language (such as Swagger), and then generate your routes and controller stubs from the description language.

 

overview2.png

 

 

 

How swagger-node works

 

You should already have some familiarity with the nodejs Expressjs web application framework to make sense of swagger-node.  A detailed explanation of Express is beyond the scope of this article.

 

In Express, you define routes to translate the path to your web app business logic.  Swagger-node uses this functionality to create your API routes.  To link the routes that are defined in the swagger.yaml to your controllers, you need to include the following swagger specification properties:

 

  • x-swagger-router-controller: The name of the controller to reference.  (In our case, this is challenge.controller)
  • operationId: The name of your function to call in the controller. (In our case, this is index)

 

Within your controller, you implement the business logic:

 

 

// challenge.controller.js

exports.index = function(req, res) {

  var currentUser = checkToken(req.headers['access_token']);

  if (currentUser.role == 'admin') {

    Challenge.find(function (err, contents) {

      if(err) { return handleError(res, err); }

      return res.status(200).json(contents);

    });

  } else {

    return res.status(403).json({ success: false, message: "Forbidden"});

  }

};

 

The swagger-node package also provides some convenience CLI commands:

 

  • swagger start project: this command will run your Swagger app
  • swagger edit project: this command will launch the Swagger editor in your browser

 

Data persistence

 

I used a Mongodb instance as the backend, and leveraged the Mongoose ORM (http://mongoosejs.com) for object modeling and DB commands.  That is the source of the Challenge.find({}) method above.

 

User and API Authentication and Authorization

 

For "time-to-market" reasons, I created a lightweight registration system within the API.  I used a package to generate random passwords to prevent participants from re-using credentials that might exist in other systems.

 

// user.controller.js

exports.createUser = function(req, res) {

    var newUser = {

    username: req.body.username,

    password: generatePassword(),

    email: req.body.email,

    role: 'user'

  };

 

  User.create(newUser, function(err, content) {

    if(err) { return handleError(res, err); }

      content.password = newUser.password;

    return res.status(201).json(content);

  });

};

 

 

To authorize access to the API, I created an access_token using JWT.

 

https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens

 

Only an API

 

This goal was satisfied, of course, by implementing the API itself via swagger-node.  I did need to publish documentation on how to use the API, and so that was the only "user interface" that was supplied.  To achieve that, I used Express' static paths.

 

app.use(express.static('static'));

 

app.get('/', function(req, res) {

  return res.redirect('/docs');

});

 

app.get('/docs', function(req, res) {

  fs.readFile(config.appRoot + '/static/howto.md', 'utf8', function (err, data) {

    if (err) console.log(err);

    try {

      var html = marked(data);

    } catch (err) {

      console.log(err);

    }

    return res.send(html);

  });

});

 

 

Package in a “cloud native” way

 

Of course, I needed a place to run the API, and leveraged the cisco-shipped.io project.  The Shipped Project is a full CI/CD pipeline for containerized apps.  I packaged my app using Docker.

 

A couple of lessons learned around Docker.  I found it inconvenient to have to remember all of the docker command syntax.  I came across this article that provided a template Makefile for common Docker CLI commands.  This was fantastic!  As the article says, it really sped up my workflow.

 

To run Mongo locally in a container, I grabbed the official version from Docker Hub, and used the "--link" flag to link the named Mongo container to my application container.  Makefile snippet for the application container:

 

# Makefile

LINK: --link $(MONGO):mongo

 

run:

  docker run --rm --name $(NAME)-$(INSTANCE) $(LINK) $(PORTS) $(VOLUMES) $(ENV) $(NS)/$(REPO):$(VERSION)

 

 

Snippet for Mongo container:

# Makefile

MONGO_PORT = -p 27017:27017

mongostart:

  docker run --name $(MONGO) $(MONGO_PORT) -d mongo

 

Other references that I found useful while creating this project:

 

How to do password authentication in Mongoose:

http://blog.mongodb.org/post/32866457221/password-authentication-with-mongoose-part-1

 

How to implement a password reset

http://sahatyalkabov.com/how-to-implement-password-reset-in-nodejs/

 

Dotenv files for environment variable management

https://marcqualie.com/2015/07/docker-dotenv

 

Unsolved problem

 

To score the answers, I never came up with a great way to validate answers.  Some of the answers required a bit of human assessment to determine if they were correct.  Obviously, choosing your questions and how the responses will be submitted will govern some of the implementation here, and I'd love to hear if anyone has accomplished a way to programmatically validate answers.

 

 

Summary

 

I learned a lot on this project.  First of all, if you are looking to inspire and motivate curious people on your team or in your company (or maybe even customers) to learn about APIs, creating a game is really useful.  From a technical perspective, I am continually amazed with how much one person can build based on the many projects and resources available on the web.  Finally, this week was Denver Startup Week (https://www.denverstartupweek.org), and I happened to attend a session presented by 5280mark that mapped really well to the DevNet API Challenge.  I didn't realize that there was a whole category of online games called Alternate Reality Games.  You can read more about how Google implemented their Firebass challenge.  I'm looking forward incorporating some of these concepts into the next DevNet API Challenge!