Anatomy of my MEAN App


Over the last couple of months, I've been working on an app using the MEAN stack to track strength over time. Ironically, I've been spending my after-hours time tinkering in JavaScript technology instead of exercising, so I've gotten a bit out of shape :-P

Before focusing on my next off-hour project, I'd like to give a rundown of some of the more interesting parts and lessons. I've posted some snippets, but you can see it all together on strength tracker's github page.

First of all, here's what it looks like:
StrengthTrackerFinal

Starting Quickly with Angular Seed

I used the github project angular-seed to get me going. It sets up the basic components you need to develop an angular app, such as jasmine and protractor for tests, and puts out some very basic components such as a directive (for showing version), and a couple of basic controllers that just show text that you can navigate between. Just clone the project, install the required dependencies with npm, and your are good to go.

For adding the rest of the stack (Mongo, Node, Express), I just used the express generator, which laid out the express directory structure and files to get a basic server running. I then copied my angular source tree into the "public" directory.

I played with mean.js, and used its yoeman generator to create a full stack skeleton, but I found that it gave me much of what I wanted to learn how to do myself, such as authentication with passport and connecting to mongo. Therefore I scrapped it for this project, though I think its a good option for getting started on a MEAN app quickly.

Authentication with PassportJS

I wanted to add some really base user registration and authentication, so I used PassportJS. Passport provides a plugin authentication architecture with some existing implementations such as basic userid/password, Twitter, and Google. While passport provides value by giving you multiple options for authentication, I'd pass on it in the future if I'm just doing local basic userid and password authentication. Most of the things I did in this stack ended up being fairly easy, but the couple of times I got hung up for an hour or 2 was with passport. I found 2 areas of difficulty.

First is that passport requires a specific order of loading its various parts interwoven within the larger node startup. If anything is out of order, then your authentication or server startup fails with unclear errors.

app.use order:

  • Express basics(express, cookie parser, bodyParsers)
  • session(with the right sauce)
  • passport(init and session)
  • authentication route(with passport strategy setup)
  • other routes

Express Setup with PassportJS - Order matters!

app.use(logger('dev'));  
app.use(express.static(path.join(__dirname, 'public')));  
app.use(cookieParser());  
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: false }));  
app.use(session({  
  secret:"keyboard cat",  
  resave:false,  
  saveUninitialized:false,  
  cookie: {  
    maxAge: 60000  
  }  
}));  

app.use(passport.initialize());  
app.use(passport.session());  
//the auth route includes passport strategy setup, which as to happen before  
//other routes, and after passport initialization  
app.use('/auth', auth(passport));  
app.use('/exercises', exercises(passport));  
app.use('/users',users(passport));  

Secondly, I had some initial trouble interweaving the functions needed for session persistence of logins. To login using session persistence, you need to add deserialize and serialize methods. These will interplay with calls you make to the server and with your login.

  1. route handles login post, calls passport.authenticate
  2. authenticate calls the login strategy function you provide to determine if the user exists and can login given the submitted credentials
  3. callback given with the authenticate call is invoked, sending the appropriate response, 401 vs successful response and payload.
  4. user id is serialized into the session so future calls can be authenticated (until the user logs out or the session expires.
  5. the login function that passport attaches to the request gets invoked, user serialization call is invoked
  6. finally the function that you provide to the rest login gets invoked. If you see that the login is successful (assuming you've called the "done" function with the right stuff), then you can process that result and send the appropriate data / response code.

Here's the code. Could be refactored to make thing's clearer, but this gives you an idea of the complexity

router.post('/login', function(req, res, next) {  
      passport.authenticate('local', function(err, user, info) {  
        if (user) {  
          req.logIn(user, function(err) {  
            if (err) {  
              throw err;  
            }  
            return res.json(user);  
          });  

        } else {  
          res.sendStatus(401);  
        }  

      })(req, res, next);  
  });  

  router.post('/logout', function(req, res, next) {  
    req.logout();  
    return res.sendStatus(204);  
  });  

  passport.use(new LocalStrategy(  
    function(userid, password, done) {  
      db.collection('users').findOne({userid:userid, password:password}, function(err, result) {  
        if(err) {  
          throw err;  
        }  
        if (result) {  
          done(null, result);  
        } else {  
          done(null, false);  
        }  
      });  
    }  
  ));  

  passport.serializeUser(function(loggedInUser, done) {  
    done(null, loggedInUser.userid);  
  });  

  passport.deserializeUser(function(id, done) {  
    db.collection('users').findOne({userid:id}, function(err, result) {  
      done(null, result);  
    });  
  });  

End to End Angular Tests with Protractor

Protractor is an end to end testing framework that combines Jasmine and WebDriver. It operates similarly to cucumber, plus it has some AngularJS specific APIs. I was able to quickly get login functionality and some navigation and DOM verification working. I also created some data setup/teardown javascript objects so that users and workouts exist for the test and are cleaned up after.

The setup/teardown code was fun (in a geeky way). I created state objects for my two main data structures in the app: users and exercises. The state object simply takes a json object of whatever data I want to set up and implements database setup and teardown for that object:

var userState = function UserState(user, dbClient) {  
  return {  
    setup:function() {  
      dbClient.insert('users', user, function(result) {  
        console.log('inserted test user');  
      });  
    },  
    teardown:function() {  
      dbClient.remove('exercises', {userid:user.userid}, function(result) {  
              dbClient.remove('users', {userid:user.userid}, function(result) {  
                console.log('removed user %s, and all associated exercises', user.username);  
              });  
      });  
    },  
  };  
};  

Then at runtime, I call setup and teardown before and after the test/tests. In this case, I set up and tear down my user before and after all tests are run:

  beforeAll(function() {  
    userState.setup();  
    exerciseState.setup();  
    browser.get('index.html');  
  });  
  afterAll(function() {  
    userState.teardown();  
  });  

You can see it all together in my end to end tests directory.

Tight Feedback with Gulp and NodeMon

One thing I really enjoyed about getting back into the javascript ecosystem is how quick I got feedback on my changes. To avoid having to restart node when I made changes, I installed nodemon, a node server that will detect changes and restart itself. Additionally, I started using gulp to lint and execute unit tests as I make changes. It was extremely easy to set up a watch to look for code changes and run unit tests and lint immediately.

Here's all the gulp code you need to get continuous linting with jshint and testing with karma:

gulp.task('lint', function() {  
  return gulp.src(files).  
    pipe(jshint()).  
    pipe(jshint.reporter('jshint-stylish'), {verbose:true});  
});  

gulp.task('test', function(done) {  
  karma.start({  
    configFile: __dirname + '/karma.conf.js',  
    singleRun:true  
  }, done);  
});  

gulp.task('watch', function() {  
  gulp.watch(files, ['lint', 'test']);  
});  

Easy Charts with Chartist

Chartist is a library build on top of D3. I saw a conference talk on it and it looked interesting, so I used it for my charting component. It already has an angular directive, so getting the charts to show up was just a matter of setting some simple json to the scope. It has databinding so the chart updates magically as you update values in the UI.

Styled Web Widgets with Angular Bootstrap

I wanted to use some bootstrap components, such as its alert box, dropdown and dialog box. I was able to leverage existing bootstrap angular directives.

Alerts

Alert

Modals

Modal

Dropdown