HTTPS SSH
# SEAS Node.js / AngularJS Base Project A base Node.js / AngularJS application for the development team at SEAS. The goal of this project is to act as a quick and easy starting point for new applications. It aims to provide: - **A reusable build process**: vendor dependency management, LESS processing, ES6 processing, live reloading, compilation for production - **Common functionality**: CAS authentication, Express server, logging, MongoDB / Mongoose - **Project organization**: source folder organization, app organization by state - **Code guidelines**: JSHint, JSCS, JSDoc Babel is used throughout the project, even in the build process itself, so feel free to make use of ES6 features everywhere. ## Table of Contents - [Requirements](#markdown-header-requirements) - [Setup](#markdown-header-setup) - [Building](#markdown-header-building) - [Running the Server](#markdown-header-running-the-server) - [Source Code / Directory Structure](#markdown-header-source-code-directory-structure) - [Client Files](#markdown-header-client-files) - [Server Files](#markdown-header-server-files) - [Common Files](#markdown-header-common-files) - [Build Process](#markdown-header-build-process) - [Vendor Files](#markdown-header-vendor-files) - [Asset Processing](#markdown-header-asset-processing) - [LESS Processing](#markdown-header-less-processing) - [JS Processing](#markdown-header-js-processing) - [HTML Processing](#markdown-header-html-processing) - [Conventions](#markdown-header-conventions) - [Angular and Browserify Modules](#markdown-header-angular-and-browserify-modules) - [Angular Naming Conventions](#markdown-header-angular-naming-conventions) - [AngularUI Router Views](#markdown-header-angularui-router-views) - [LoadingTask / LoadingManager](#markdown-header-loadingtask-loadingmanager) - [Linting / Code Style](#markdown-header-linting-code-style) - [Documentation](#markdown-header-documentation) - [CAS Authentication](#markdown-header-cas-authentication) ## Requirements In order to allow for sessions to be managed across multiple backend servers and/or processes, you will need to have a MongoDB server set up and running. Connection parameters to the MongoDB server should be set in `src/server/config.js`. ## Setup Install [Node.js](http://nodejs.org/). Install [Bower](http://bower.io/) and [Gulp](http://gulpjs.com/) globally: ```sh npm install -g bower gulp ``` Clone the Git repository and install the dependencies: ```sh $ git clone git@bitbucket.org:harvardseasdev/seas-node-angular-base.git $ cd seas-node-angular-base $ npm install $ bower install ``` Make a copy of the default configuration file and adjust settings for your environment: ```sh $ cp src/server/config_default.js src/server/config.js ``` You will need to create a User object in your MongoDB *users* collection to allow for authentication. Model specs can be found in `src/server/mongo/models/User.js`. This concludes initial setup. Read on for how to build the project and run the server. ### Building This project uses [Gulp](http://gulpjs.com/) to run its build process. All build-related files are located in the `gulp/` folder. The configuration file for the build process is `gulp/config.js`. Typically, this file is only modified to add/remove Bower dependencies. The build process exposes the following commands: ``` npm run build npm run watch npm run dev npm run compile ``` - **build**: Builds the project for development and puts it in the *BUILD_DIRECTORY* (`build/` by default). - **watch**: Runs *build* and then watches the client files. If any changes are made to client files, those files are rebuilt and a [LiveReload](http://livereload.com/) is triggered. (It should be noted that the *watch* task only watches `src/client/` and that changes in `src/common/` will not trigger a rebuild.) - **dev**: Runs *watch* and additionally launches the server. If any changes are made to server files, the server is relaunched. - **compile**: Builds the project for production and puts it in the *COMPILE_DIRECTORY* (`compile/` by default). ### Running the Server To run the server, simply use the following command: ``` npm start ``` The server will be started and logs will be stored in `src/server/logs/`. If *DEV_BUILD* is set to *true* in `src/server/config.js`, it will serve static files from the build directory. If it is set to *false*, it will serve static files from the compile directory. ## Source Code / Directory Structure This section explains the methodology behind the way the source code is organized and acts as a reference for locating specific folders and files. The directory structure is laid out as such: ``` src/ client/ components/ AppClasses/ AppData/ AppDirectives/ ... less/ ... states/ app.less app.js app.tpl.html ... index.less index.js index.html server/ lib/ Logger.js express/ routes/ ... index.js mongo/ models/ ... methods/ ... index.js index.js boot.js worker.js server.js config_default.js common/ ... ``` ### Client Files `src/client/` is where all of the client files are located and it is the directory upon which the build process operates. **States** are the core concept behind the organization of this directory. The idea is that the files should be organized so that everything specific a single *state* is in one place: LESS, JS, and HTML. Furthermore, the directories should be organized so that they resemble the hierarchy of the states themselves. The very base state is called *app* and its files are located in the `src/client/states/` folder. All application states should have their own folder inside of the folder for their respective parent state. If you need to break out your state files into more than a single file of each type, you can come up with your own scheme for organizing these; the concept of hierarchical state folders should still be the main organization technique. The following is an example of what a more nested state structure might look like: ``` states/ states/ admin/ admin.less admin.js admin.tpl.html directory/ group/ person/ person.less person.js person.tpl.html group.less group.js group.tpl.html directory.less directory.js directory.tpl.html app.less app.js app.tpl.html ``` So where does everything else go? Everything that isn't a state is a **component**. Theoretically, many components could be reused across projects, but a component just has to be something that is not associated with a particular state and is required by the application (e.g. an interface for interacting with an API). The components in the base project are organized categorically. Each category of component is its own Angular module and has a folder in `src/client/components/`. The component categories in the base project are separated as follows: - **AppClasses**: This is just a module to import the class files from the `src/common/` folder and convert them to Angular factories, so they can be injected as dependencies in other modules. - **AppData**: This module handles interactions with the API. It has some reusable components, like the *LoadingTask* and *LoadingManager* classes, and the corresponding *loadingOverlay* directive. It also contains the *APIService*, which acts as a single location for defining API interactions. - **AppDirectives**: This is a general module for holding all custom directives. This is arguably the most reusable component category, but it could also contain directives specific to the application (e.g. a form that needs to be used in multiple locations). Do not feel restricted by the existing categories; these can be organized in any way you want. ### Server Files `src/server/` is where all of the server files are located. Most of the server files export functions as to utilize dependency injection and maintain reusability. In the base directory are the files `index.js`, `server.js`, and `boot.js`. An explanation of these files follows: - **index.js**: This is the entry file for starting the server. All it does is initialize Babel and then run *boot.js*. This is necessary because Babel cannot be initialized and used in the same file in which it is imported. - **boot.js**: This is the file that sets up the master process for the server. It employs [Cluster](https://nodejs.org/api/cluster.html/) to take advantage of multiple processors. It runs *worker.js* in each of these processes. - **worker.js**: Much like **index.js**, this is the entry file for starting a worker. All it does is initialize Babel and then run **server.js**. This is necessary because Babel cannot be initialized and used in the same file in which it is imported. - **server.js**: This is the true starting point for the server code, and where most high-level changes will be made. It makes the calls to set up all of the server components. The server also comes with a singleton *Logger*, located at `src/server/lib/Logger.js`. This is set up as a global variable and should be used for all of the server side logging. It provides various log levels (*error*, *warn*, *info*, *debug*), appends useful time and stack information, and has nice color coding when writing to the console. It is also responsible for writing logs to the appropriate files. **Core components** are the core concept behind the organization of this directory. Basically, each piece of the server stack has its own folder where it handles its own responsibilities. The two core components in the base project are *Mongoose* and *Express*. The core components of the base project are as follows: - **Mongoose**: This component is responsible for connecting to MongoDB and setting up the required Mongoose models and methods. - `src/server/mongo/models/` contains files that export Mongoose models. The filename should be the same as the name of model being exported. - `src/server/mongo/methods/` contains files that export Mongoose methods for the models of the same name. Again, the filename should match the model name. - All of the models and methods in these folders are automatically scanned and registered with the Mongoose connection. - **Express**: This component is responsible for setting up the Express app and defining all of the routes that will be served. - `src/server/express/routes/` contains organized subdirectories and files that set up the routes for the app. These can be organized in any way you want. Other potential core components would be different data stores such as *MySQL* or *Redis*, or different communication protocols such as *Socket.IO*. ### Common Files `src/common/` is where all JS files needed by both the client and the server are located. Typically these are classes or libraries that employ logic that must be maintained in both locations, avoiding duplication of code. The files within this directory can be organized in any way that makes sense for a given project. ## Build Process This section expands on the specific details of the build process and contains important information to keep in mind when making changes to the project. ### Vendor Files Vendor dependencies are installed using [Bower](http://bower.io/) but simply installing a Bower module will not include any files in the build process. The files that need to be included in the build process should be added to the *VENDOR_FILES* object in `gulp/config.js`, using paths relatives to `bower_components/`. They are grouped by *ASSETS*, *CSS*, and *JS*. This is typically the only thing that needs to be modified in the `gulp/config.js` file. ### Asset Processing Any files in the `src/client/assets/` folder will simply be copied to the `assets/` folder in the final build. ### LESS Processing All LESS files are compiled and run through [Autoprefixer](https://www.npmjs.com/package/autoprefixer/), so there is no need to use vendor prefixes with compatible properties. Any URLs you need to reference in the CSS that are located in the `src/client/assets/` folder should be referenced as though the `assets` folder were in the same directory (e.g. `background-image: url('assets/bg.png');`). It is important to note that, by default, the client CSS is compiled from a single entry file: `src/client/index.less`. Your *@include* calls from that file will determine which files are included in the compilation. ### JS Processing All client JavaScript is processed using [Browserify](http://browserify.org/) and [Babel](https://babeljs.io/), so using ES6 features is encouraged. When the code is minified for production, [ng-annotate](https://www.npmjs.com/package/ng-annotate/) is used so that the mangling of function arguments doesn't disrupt the automatic AngularJS dependency injection. In general, it can automatically detect when this is needed. However, if a function that relies on Angular dependency injection is imported from a different file, it must have a special comment preceding its declaration: `/*@ngInject*/`. See `src/client/components/AppDirectives/` for examples of this. It is important to know that, by default, the client JavaScript is compiled from single entry file: `src/client/index.js`. Your *require* calls from that file will determine which files are included in the compilation. Also note that Browserify is set up to use `src/` as a valid root, in addition to `node_modules/` (e.g. if you want to require `src/common/{filename}.js`, you can use `require('common/{filename}.js');` from anywhere). ### HTML Processing All of the HTML templates in the project have the extension *.tpl.html*. The inclusion of these files in the build process is automatic; all *.tpl.html* files within `src/client/` are included in the build process. The files themselves are not included in the final build, however. [ng-html2js](https://www.npmjs.com/package/ng-html2js/) is used to convert the HTML files into strings that are stored inside an Angular module called "appTemplates". This lets us avoid making HTTP requests for individual template files. All of the files can be used as templates inside Angular by referencing their path relative to `src/client/`. See the `src/client/states/` directory for examples of how to set up states and their templates. ## Conventions This section explains some of the conventions used in the project, with the intention of providing a framework for consistency moving forward. ### Angular and Browserify Modules At first glance, the Angular module system and the Browserify module system would seem to be at odds; they both do similar things in different ways. One could potentially ignore the Angular module system and simply *require()* all of the necessary files. There is a convention established in this project that blends the two. When listing an Angular module's dependencies, *require()* calls are used as follows: ```js angular.module('Module', [ require('otherModule.js').name ]); ``` The imported file should export an Angular module. What the above code does is import the code from that file, making it a part of the codebase, and then simply take the *name* attribute from the imported module and give it to the new module as a dependency. ### Angular Naming Conventions The project employs a specific naming convention for both Angular modules and AngularUI Router states. Angular module names are capitalized and hierarchical. Thus, a module *Team* would have a child *Team.Person*. The same goes for state names. The state names for these would be *team* and *team.person*. This is just a recommendation, although it should be noted that the state names do have a functional purpose; AngularUI Router states named in this way are automatically assigned the correct parent, instead of having to specify `parent: 'team'`. ### AngularUI Router Views AngularUI Router states use a *ui-view* directive to determine which element they bind to. This directive does not require an argument but can accept one to assign the associated view a name. If there is no name, states automatically populate any *ui-view* within their parent. These named views would only be absolutely necessary in a case where two states exist alongside each other at the same level. Even though this is not the case in the base project, named views are always used to eliminate ambiguity. See the the *views* property of state definitions in this project for more information. You can also read more about AngularUI views [here](https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views). ### LoadingTask / LoadingManager Two useful classes are provided in the base project: *LoadingTask* and *LoadingManager*. These are designed to make API calls as simple and elegant as possible. Descriptions follow: - **LoadingTask**: Essentially a wrapper for an Angular HTTPPromise that implements a lot of common needs. - Makes API calls relative to the base API URL, eliminating the need for repetition of the full route. - Can automatically transform responses using a given function (e.g. instantiating a class using the retrieved data before resolving). - Handles the server's response and makes it nice for the promise resolution. On success, just resolves with the data itself instead of the full response. On failure, takes the *error* property of the returned object and rejects with just the error string. - See `src/client/components/AppData/APIService.js` for examples. - **LoadingManager**: A class to aggregate *LoadingTasks* and keep track of them as a group. - Provides methods for waiting for multiple *LoadingTasks*. More can be added and the *LoadingManager* will take them into account before resolving. - See `src/client/index.js` for example. - **loadingOverlay**: A directive to display the status of a *LoadingManager*. - When tasks are in progress, shows a loading spinner and a description of the tasks. Hides itself when no tasks are in progress. - See `src/client/index.html` for example. The *LoadingManager* is also taken advantage of to implement sequential loading based on state. If you look at `src/client/index.html`, you'll see that an *ng-if* is used on the main *ui-view*. This ensures that no child state will be loaded before the *LoadingManager* is done loading. This allows for certain assumptions, like knowing you'll have access to the user data once the *app* state loads. This is a common pattern for state templates in this project: ```html <loading-overlay loading-manager="loadingManager"></loading-overlay> <div id="app" class="fade" ng-if="appInitialized" ui-view="app"></div> ``` The scope variable *appInitialized* is just a boolean that is set to true when the *LoadingManager* first finishes loading (see `src/client/index.js`). ### Linting / Code Style **JSHint** is run on any JS that is processed. The build process will alert you of JSHint findings but these warnings will not crash the build. The settings for JSHint can be found in `.jshintrc`. Although there are defaults in the JSHint settings, one thing that may need to be modified from time to time is the *globals* property. Normally JSHint will throw an error if you try to use a variable that is not defined in a given file. This *globals* property is an array of values that will not trigger this error. For example, "angular" needs to be included in this array, as it defined in the vendor file and then used globally. Code style is merely a recommendation. It is not enforced in the build process. **JSCS** is used to define code style and the settings for JSCS can be found in `.jscsrc`. ### Documentation Documentation is not strictly enforced and no documentation generator is currently used but it is recommended that everything be documented following the conventions of [JSDoc](http://usejsdoc.org/). For Angular-specific conventions, see [ngDoc](http://www.chirayuk.com/snippets/angularjs/ngdoc). ## CAS Authentication The Express server implements CAS authentication using the [cas-authentication](https://www.npmjs.com/package/cas-authentication/) library, which was originally created specifically for this project. CAS is set up in `src/server/express/index.js` and implemented as middleware and endpoints in `src/server/express/routes/index.js`. There is some additional work done with CAS authentication in `src/server/express/routes/api/index.js`. The middleware implemented there protects the entire API route: - If the user is not authenticated with CAS, it responds with a code 401 Unauthorized. - If user is authenticated and there is no stored session property *user_accessLevel*, it checks MongoDB for an associated *User* and stores this data. - If the user is authenticated with CAS but has no entry in MongoDB, it responds with a code 401 Unauthorized response. - If the user is authenticated and has an entry in MongoDB, it then passes through the whatever route the user was requesting. On the client side, there is a file `src/client/components/AppData/APIInterceptor.js`. This is an Angular [\$http interceptor](https://docs.angularjs.org/api/ng/service/$http/#interceptors). It intercepts all \$http requests and does the following: - On successful response: - Allows template file requests to pass through. - If there is an *error* property on the response data (this is how the server reports errors), returns a rejected promise with the error. - Otherwise, just returns the response data (so \$http requests will just return the data itself, and not the whole response object). - On error response: - If the response status is 401, redirects to the URL for CAS authentication. - Otherwise, just returns a rejected promise with an error string. The redirection on 401 is important because the app can stay open, making API calls, and once it gets a 401 response it will redirect to the login. Otherwise, the server would have no way to force the client to go to the CAS login from an API call. Most CAS configuration can be done in the server configuration file located at `src/server/config.js`. When developing, *CAS_DEV_MODE* can be set to *true* and the server will pretend to do CAS authentication and use the *CAS_DEV_USER* string as the retrieved HUID.