Pick anyandgo!

Anyandgo MEAN Framework

The MEAN Framework that doesn't suck.

What is the MEAN stack?

The acronym stands for:

  • (M)ongoDB – a noSQL document datastore which uses JSON-style documents to represent data,
  • (E)xpress – a HTTP server framework on top of Node,
  • (A)ngular – as you know, the JS framework offering declarative, two-way databinding for webapps and
  • (N)ode – the platform built on V8’s runtime for easily building fast, scalable network applications.

Read More

Quick Setup

How to get started with anyandgo?

Install anyandgo client

$ [sudo] npm install -g anyandgo-cli 

Create a new project

 $ anyandgo init mynewproject
   mynewproject /var/www
   https://nodeload.github.com/cortezcristian/anyandgo/zip/master
   You have successfully created the anyandgo project
   Try:
        cd mynewproject
        npm install && bower install && grunt

To get the most out of anyandgo you may also want to install the following

$ npm install -g grunt-cli bower yo mocha

Best Practices

To start working you can simply run:

$ grunt

This will:

  • ✓ Lint the js code
  • ✓ Run the tests
  • ✓ Start the server
  • ✓ Open a web browser
  • ✓ Watch for files changes, to trigger several tasks

To add front-end libraries you can simply run:

$ bower install --save jquery

This will:

  • ✓ Register the dependency into bower.json
  • ✓ Download the library inside ./public/components/
  • ✓ Append the script into ./views/layout.jade
  • ✓ Trigger page reload

Grunt tasks

The default grunt task will start the server for you.

$ grunt

Lint javascript files under models, routes and test folders

$ grunt jshint

Execute all mocha tests and display the specs report

$ grunt mochaTests

Creates documentation functionallity under models, routes and test folders and put it inside docs folder

$ grunt docco

Appends javascript and css dependencies

$ grunt wiredep

Scaffolding

Model+Test generation

$ grunt create:model:Sample

Will create model and tests:

models/sample.js

test/unit/models/sample-tests.js

Will modify ./routes/main.js to append the model as dependency
// ## Models
/* models:start */
+ Sample  = require('../models/sample.js'),
/* models:end */

This will automatically crete the following tests:

$ mocha test/unit/
Database Test
  MongoDB
   ✓ Should be up and running 
...
Model Test Sample
  Sample
   ✓ add a sample 

Page+Route generation

$ grunt create:page:Contact

Will create a public view file for the page:

views/contact.jade

Will modify ./routes/main.js to append the model as dependency

/* page:public:start */
+  
+  // ### Contact Page
+  app.get('/contact', function (req, res) {
+    res.render('contact', { title: 'Contact', section: 'Contact' });
+  });
/* page:public:end */

Will modify ./views/partials/site-menu.jade to append the new menu item to main menu

// public:page:menu:start
+        li
+          a(href='/contact') Contact
       // public:page:menu:end

Rest+Test generation

$ grunt create:rest:Sample

Creates rest services for a particular model.

Will modify ./routes/main.js to append the model as dependency

/* rest:public:start */
+
+// GET /api/v1/samples
+restify.serve(app, Sample, {
+  lowercase: true,
+  lean: false,
+  |rereq: function(req) {
+    console.log("pre req");
+    return true;
+  },
+  contextFilter: function(model, req, cb) {
+    console.log("context filter");
+    cb(model);
+  },
+  |ostProcess: function(req, res){
+    console.log("post process");
+  }
+});
/* rest:public:end */

Along with a test file:

test/rest/models/samples-rest-tests.js

This uses superagent to test the new restful api:

$ mocha test/rest/
Web Server
Express
✓ Should be up and running (306ms)
...
REST API Sample http://127.0.0.1:3000/api/v1/samples
Samples REST
✓ GET /api/v1/samples 
✓ GET /api/v1/samples/count 
✓ POST /api/v1/samples 
✓ PUT /api/v1/samples/:sampleId 
✓ DELETE /api/v1/samples/:sampleId 
✓ DELETE /api/v1/samples 

This will enable the following urls:

GET      /api/v1/samples/count
GET      /api/v1/samples
PUT      /api/v1/samples
POST     /api/v1/samples
DELETE   /api/v1/samples
GET      /api/v1/samples/:id
PUT      /api/v1/samples/:id
POST     /api/v1/samples/:id
DELETE   /api/v1/samples/:id

Learn more about query, ordering, populate, and sorting with Express-Restify-Mongoose.

Crud+Test generation

$ grunt create:crud:Sample

Creates CRUD administration for a particular model. This uses zombiejs to test the new crud functionality.

Once you create the model and all the rest services you'll be able to generate a crud automatically. Let's imagine you need to create a CRUD for students.

$ grunt create:model:Student
$ grunt create:rest:Student
$ grunt create:crud:Student

If you go to to http://localhost:3000/admin/panel login as administrator, and click on the Crud dropdown:

If you select Student, you'll be able to see the list where you can create, edit and delete:

Just hit edit on one record

Now let's change our modelmodels/student.js to add a new field called age:

// Student Model
// -----------------------------
 
// Modules Dependencies:
//  - Mongoose (http://mongoosejs.com/docs/guide.html)
//  
var mongoose = require('mongoose'), 
    Schema = mongoose.Schema;
 
var studentSchema = new Schema({
    name          : String, 
    age          : Number,  /* <-- add this line to the model */
    created       : Date         
});
 
// [...] More code here

After you save the file go to the administration panel again http://localhost:3000/admin/panel you'll need to login again (if you have not enabled autologin yet). If you go to edit or create you'll see the form changed a little bit:

As you can see the form has been autogenerated, to be according to your little change in the model.

AutoLogin for developers

Is very annoying when you are developing that you need to login every time the server reloads, so we included a feature for autologin just go and modify config/config-local.json search for this property:

"autologin": {
    "enabled" : true, <-- Set this to true, by default is false
    "username" : "admin@anyandgo.com",  
    "password" : "123456"  
},

Setting autologin.enabled to true will automatically put user and password on the login form and hit login for you to be redirected to the last url you were looking in the adminnistration panel.

Custom Forms Fields

You can easily implement, custom fields for your automatic generated forms. In example:

// Sample Model
// -----------------------------
 
// Modules Dependencies:
//  - Mongoose (http://mongoosejs.com/docs/guide.html)
//  
var mongoose = require('mongoose'), 
    Schema = mongoose.Schema;
 
var sampleSchema = new Schema({
    name          : String, 
+    template      : { type: String, ngoform: { control: 'Textarea' } },
+    live          : { type: Boolean, default: true, ngoform: { control: 'Toggle' }    },
    created       : Date         
});
 
....

Will produce, the following:

For the Toggle control is as simple as adding a file called ./utils/formstemplates/Toggle.hbswith the following:

<div class="control-group { {#if error} }error{ {/if} }"> 
    <label class="control-label">{ {label} }</label>
     <div class="controls">
         <toggle-switch { {#if ngmodel} }ng-model="{ {ngmodel} }.{ {name} }"{ {/if} } on-label="true" off-label="false"><toggle-switch>
     </div>
 </div>

Download the package with bower and register the dependency into public/scripts/admin/app.js:

$ bower install --save angular-toggle-switch

In our app.js file just add this line:

/**
 * @ngdoc overview
 * @name anyandgoApp
 * @description
 * # anyandgoApp
 *
 * Main module of the application.
 */
angular
  .module('anyandgoApp', [
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngRoute',
    'ngSanitize',
    'ngTouch',
+    'toggle-switch',
    'restangular'
])
.config(function ($routeProvider, $locationProvider, RestangularProvider) {
.....

Locale+Translation File generation

$  grunt create:locale:es-es

Creates a new file under translation folder called ./locales/es-es.json. Adds translation flag to the menu, modifying ./views/partials/site-menu.jade:

         //public:translation:menu:start
         li
            a(href='#', langsupport="en-us") en-us
          li
            a(href='#', langsupport="es-ar") es-ar
+            li
+              a(href='#', langsupport='es-es') es-es
          //public:translation:menu:end

Also registers new language into app.js:

i18n.configure({
// setup some locales: other locales default to en silently
locales:[
    //global:translation:start
+      'es-es',
    //global:translation:end
    'en-us', 
    'es-ar'],

See the result inmediately in your browser:

How to use translations just open a view file views/index.jade:

extends layout
block content
.jumbotron
 h1=__("pick anyandgo")
 p ...
 p Bienvenidos a anyandgo MEAN

Basically, everytime you call to function double underscore what you are passing as parameter is used as key for translation files. Translation files will auto populate it everytime you refresh the page calling the view.

Optimization

Assets minification for production

$ grunt buildprod

Concatanates, compress, minify and link all javascripts and stylesheets. It creates a ./dist folder inside the public part:

$ tree public/dist/

public/dist/

├── scripts

│ ├── panel-app.min.js

│ ├── panel-vendors.min.js

│ └── site-vendors.min.js

└── styles

├── panel-styles.min.css

└── site-styles.min.css

We use: grunt-usemingrunt-contrib-clean grunt-contrib-concat grunt-contrib-uglify grunt-contrib-cssmin in order to do that.

And they get automatically linked just simply by setting a global flag, like in ./views/layout-admin.jade:

- if(settings.envflag !== "production") {
    //-
    //bower:css
    link(rel='stylesheet', href='/components/bootstrap/dist/css/bootstrap.css')
    link(rel='stylesheet', href='/components/font-awesome/css/font-awesome.css')
    link(rel='stylesheet', href='/components/metisMenu/dist/metisMenu.css')
    //-
    //endbower
    - } else {
    link(rel='stylesheet', href='/dist/styles/panel-styles.min.css')
    - }

The flag setup is in ./app.js:

 

app.set("envflag", process.env.NODE_ENV);

Translation Support

Internationalization support was added. To seee the translations source files you can inspect the ./locales folder:

$ tree locales/

locales/

├── en-us.json

└── es-ar.json

By default it loads es-ar translations, the configuration is inside app.js file:

// i18n setup
i18n.configure({
// setup some locales: other locales default to en silently
locales:['en-us', 'es-ar'],
// sets a custom cookie name to parse locale settings from  - defaults to NULL
cookie: 'lang',
// where to store json files - defaults to './locales' relative to modules directory
directory: __dirname + '/locales',
defaultLocale: 'es-ar'
});

Language preference is stored in a cookie, to test this is working you can try modifying the cookie on client side and reload the page:

// Try running this in the js console
function setCookie(cname, cvalue, exdays) {
    var d = new Date();
    d.setTime(d.getTime() + (exdays*24*60*60*1000));
    var expires = "expires="+d.toUTCString();
    document.cookie = cname + "=" + cvalue + "; " + expires;
}
 
setCookie("lang","en-us", 2);
// and refresh the page, notice lang has changed to be en-us

Fixtures

Fixtures are fixed datasets that helps us to populate mongo collections. We use fixtures during test, to ensure we have data to operate with. And also anyandgo uses fixtures on server start, to ensure certain collections are filled in before web app is launched. Take a look at app.js:

// DB Fixtures
if (config.fixtures && config.fixtures === "enabled") {
 // Load Fixtures
require('./fixtures');
}

For fixture loading we are using mongoose-fixtures. We basically load different datasets for each environment. You may want to take a look at ./fixtures folder:

$ tree fixtures
... 
fixtures/
├── dev
│   └── admins.js
├── index.js
├── local
│   └── admins.js
├── prod
│   └── admins.js
├── shared
│   └── admins.js
└── travisci
    └── admins.js
 
5 directories, 6 files

Notice that if flag fixtures is enabled in our config: anyandgo will autoload datasets for each collection ( note loading fixtures will clear the existing contents of a collection). In the treeview example shown above, we only are going to override admins collection. We are also adding a shared folder that is shared for all environments.

Mails

Mail support was added, using nodemailer and nodemailer-smtp-transport. See the configs:

"mail" : {
        "enabled" : true,
        "transport" : "smtp",
        "host" : "mail.your-email-host.io",
        "port" : 25,
        "contact" : "contact@email.io",
        "auth" : {
            "user" : "user-email-here",
            "pass" : "secret-pass-here"
        }
    },

Captcha Support

Using the Google reCAPTCHA Service and node-recaptcha package. You can enable this feature just by changing the config settings.

"captcha" : {
    "enabled" : true,
    "publickey" : "insert-key-here",
    "privatekey" : "insert-key-here"
},

Server Logs

By enabling config you can save the server log in a custom file, this is very useful for production environments. Just change your config file config/config-local.json to included the following:

{
 "app": {
     "domain" : "127.0.0.1",
+        "port": 3000,
+        "logs": {
+            "enabled": true,
+            "file": "access.log",
+            "format": "dev"
+         }
    },
"auth": {
ParameterDescription
app.logs.enabledEnables / disables logs
app.logs.file Path to log file (access.log by default)
app.logs.format Morgan Predefined Formats

Create the log file manually:

$ touch access.log

Start the server, and then monitor the logs files by doing:

$ tail -f access.log

Security

CORS Support

See CORS

SEO Support

Added support for metadata, see ./views/layout.jade:

  block meta
      // for Google
      meta(name='description', content='')
      meta(name='keywords', content='')
      meta(name='author', content='')
      meta(name='copyright', content='')
      meta(name='application-name', content='')
      // for Facebook
      meta(property='og:title', content='')
      meta(property='og:type', content='article')
      meta(property='og:image', content='')
      meta(property='og:url', content='')
      meta(property='og:description', content='')
      // for Twitter
      meta(name='twitter:card', content='summary')
      meta(name='twitter:title', content='')
      meta(name='twitter:description', content='')
      meta(name='twitter:image', content='')

Added Google Analytics tracking configuration. See the config file:

 "analytics": {
     "enabled" : true,
     "tracking": "UA-XXXXX-X"
 },

Express 4.x

Facts about this implementation:

Modules added:

Front end assets:

Credits

Follow @cortezcristian