Overview

Welcome to the Sonate Framework documentation.

This project is still in version 0.1.x so this documentation is still pretty light. As the project get more stable, the documentation will get sharper and easier to understand for beginner developer.

To start a new project, get your hands on the latest version of Sonate Framework.

Parameters

First thing to do to setup a new Sonate project is to configure the parameters.

app config parameters.js

                    
                        var core = require('sonate-core');

                        core.parameters = {

                            // Port for your node project. e.g http://localhost:8080/
                            port: 8080,

                            // Databse settings
                            database: {
                                engine: 'mysql',
                                name: "sonate",
                                username: "root",
                                password: "root",
                                port: 8889
                            },

                            // Global mail arguments
                            mail: {
                                user: "root",
                                password: "***",
                                from: "no-reply@sonate.io"
                            },

                            // Secret token for the encryption service
                            secret: 'ThisTokenIsNotSecretChangeIt'

                        };

                        exports.parameters = core.parameters;
                    
                

Server

Nothing specific to do on the server, perfectly working as it is. Its here if you need to add specific processes or libraries to the server variable or Socket.io.

app config server.js

                    
                        var server = require('sonate-core').server;

                        // Executed on server run;
                        server.run = function() {
                            server.config(function(app, io){

                                var database = require('./database').database,
                                    security = require('./security').security,
                                    Parameters = require('./parameters').parameters;

                                // Connection to the database before continuing to the bundles
                                database.connect();

                                var bundles = require('./bundles').bundles;

                                // Here are the server settings.
                                // If you need to add processes to the server, just write it here.
                                // e.g. app.use('my custom process');
                                // All of the followings are already preseted:
                                // app.use(cookieParser());
                                // app.use(session({secret: '1234567890AZERTY'}));
                                // app.use(bodyParser.json());
                                // app.use(bodyParser.urlencoded());
                                // app.use('/web', express.static(__dirname + '/../../../../web'));
                                // app.engine('html', swig.renderFile);
                                // app.set('view engine', 'html');

                                app.use(function(req, res) {

                                    // Security check on the requested path
                                    security.check(req, res, function() {
                                        server.bundle(req.url, bundles, function(bundle) {

                                            // Distributing the request & response vars to the proper bundle
                                            bundle.bundle.exec(req, res, bundle.action);
                                        });
                                    });
                                });

                                // Socket.io connection for realtime services & events
                                io = io.listen(app.listen(Parameters.port));
                                io.sockets.on('connection', function(socket) {
                                    for (key in bundles) {
                                        if (bundles.hasOwnProperty(key)) {
                                            bundles[key].bundle.events(socket);
                                        }
                                    }
                                });

                            });
                        };

                        exports.server = server;
                    
                

Bundles

Declare & define all your bundles to structure your code.

app config bundles.js

                    
                        exports.bundles = {

                            // Simply define all your bundles
                            // with their name & path

                            Welcome : {
                                name : 'WelcomeBundle',
                                path : '/',
                                bundle: require('../../src/WelcomeBundle/WelcomeBundle').WelcomeBundle
                            },
                            User : {
                                name : 'UserBundle',
                                path : '/user',
                                bundle: require('../../src/UserBundle/UserBundle').UserBundle
                            }

                        };
                    
                

Ssecurity

When building large applications you will need to secure some pages or even entire bundles. To do that, you will need to declare paths that are available according to the user session's role.

app config security.js

                    
                        var security = require('sonate-core').security;

                        security.firewalls = {
                            // Entire bundle restricted to users
                            '/user': 'ROLE_USER',

                            // Entire bundle restricted to admin users
                            '/admin': 'ROLE_ADMIN',

                            // Paths of a secured bundle that available to everyone
                            '/user/register': 'ANONYMOUS',
                            '/user/login': 'ANONYMOUS',
                            '/user/logout': 'ANONYMOUS'
                        };

                        // Path where users are redirected if they do not have the proper role to access the requested page
                        security.redirect = '/';

                        exports.security = security;
                    
                

Routes

Here it all begins! Declare the first route of your app.

src WelcomeBundle Resources config routes.js

                    
                        var path = require('sonate-core').path;

                        exports.routes = {

                            // The key of the object is the second part of the path starting at the prefix path of the bundle defined in app > config > bundle.js
                            '/' : {

                                // The controller to call
                                controller: require(path.__CONTROLLERS__+'IndexController').IndexController,

                                action: function(req, res) {
                                    // The corresponding action of the controller, here it's index
                                    this.controller.index(req, res);
                                },

                                // Object of the allowed methods
                                // For all methods, e.g. ['GET', 'POST', 'PUT', 'DELETE']
                                method: ['GET']
                            }

                        };
                    
                

Controllers

src WelcomeBundle Controllers IndexController.js

                    
                        var Controller = require('sonate-core').Controller,

                            // Instanciate a new controller
                            IndexController = new Controller();

                            // Instanciate a new view
                            View = new require('sonate-core').View(),

                        // Define your first controller's action, e.g. index
                        IndexController.index = function(req, res) {

                            // Simply render a view
                            // The second argument is the path to the view
                            // the first part is the bundle,
                            // the second is the folder in Resources/views of that specific bundle
                            // and the third is the .html file
                            // Here e.g. /src/WelcomeBundle/Resources/views/index/index.html
                            View.render(res, 'WelcomeBundle:index:index');
                        };
                    
                

Passing a variable to the view

                    
                        // Passing a variable to the view

                        IndexController.index = function(req, res) {
                            var title = "Hello";
                            View.render(res, 'WelcomeBundle:index:index', {

                                // Pass all of your vars here
                                title: title
                            });
                        };
                    
                

Redirect

                    
                        // Redirect

                        IndexController.index = function(req, res) {
                            View.redirect(res, '/user/login');
                        };
                    
                

Views

The views are a simple HTML file. Sonate Framework uses Swig Template Engine. Check out the documentation for more information. You'll find below a simple example of how you can create a view that extends from a layout.

src WelcomeBundle Resources layout.html

                    
                        <html>
                            <head>
                                <title>{% block title %}{% endblock %} | Welcome</title>
                                // Meta tags
                                // Stylesheets
                            </head>
                            <body>

                                <header>
                                    <ul>
                                        <li><a href="/">Home</a></li>
                                        <li><a href="/a-page">A page</a></li>
                                        <li><a href="/contact">Contact</a></li>
                                    </ul>
                                </header>

                                {% block body %}{% endblock %}

                                {% block footer %}
                                    <footer>
                                        <ul>
                                            <li>Copyright ©</li>
                                            <li><a href="/team">Team</a></li>
                                            <li><a href="/legal">Legal</a></li>
                                        </ul>
                                    </footer>
                                {% endblock %}

                            </body>
                            {% block scripts %}{% endblock %}
                        </html>
                    
                

src WelcomeBundle Resources views index.html

                    
                        {% extends "../layout.html" %}

                        {% block title %}Title of the page{% endblock %}

                        {% block body %}
                            <h1>Nice title here</h1>
                            <p>Welcome on my website!</p>
                        
                            <p>Count to 3:</p>
                            // seq = [1, 2, 3, 4, 5];
                            {% for item in seq %}
                                {% if item == 1 %}
                                    <b>{{ item }}</b>
                                {% elseif item == 2 %}
                                    <i>{{ item }}</i>
                                {% else %}
                                    <span>{{ item }}</span>
                                {% endif %}
                            {% endfor %}
                        {% endblock %}

                        {% block footer %}
                            {% parent %}
                            // Add something to the footer
                        {% endblock %}

                        {% block scripts %}
                            <script type="text/javascript">
                                // Write some javascript here or import a .js file
                            </script>
                        {% endblock %}
                    
                

Entities

Working with entities is very easy with Sonate Framework as it runs Sequelizejs. Declare your entities and their repositories in the bundle configuration, then simply define it in the Entities folder.

src UserBundle Resources config entities.js

                    
                        var path = require('sonate-core').path;

                        exports.entities = {

                            /*
                             *  User entity
                             */

                            // Declare the entity
                            User: require(path.__ENTITIES__+'User').User,

                            // Declare the repository
                            UserRepository: require(path.__ENTITIES__+'UserRepository').UserRepository

                            /*
                            *  Another entity
                            */

                            // ...

                        };
                    
                

src UserBundle Entities User.js

                    
                        var database = require('sonate-core').database,
                            Sequelize = database.Sequelize,
                            sequelize = database.sequelize;

                        var User = sequelize.define('User', {

                            id: {
                                type: Sequelize.INTEGER,
                                primaryKey: true,
                                unique: true,
                                autoIncrement: true
                            },
                            email: {
                                type: Sequelize.STRING,
                                allowNull: false,
                                defaultValue: ""
                            },
                            password: {
                                type: Sequelize.STRING,
                                allowNull: false,
                                defaultValue: ""
                            },
                            firstname: {
                                type: Sequelize.STRING,
                                allowNull: false,
                                defaultValue: ""
                            },
                            lastname: {
                                type: Sequelize.STRING,
                                allowNull: false,
                                defaultValue: ""
                            },
                            roles: {
                                type: Sequelize.STRING,
                                allowNull: false,
                                defaultValue: "['ROLE_USER']"
                            }

                        });

                        exports.User = User;
                    
                

src UserBundle Entities UserRepository.js

                    
                        var manager = require('sonate-core').database.manager,
                            UserRepository = new manager.Repository();

                        UserRepository.Entity = require('./User').User;

                        manager.register('MyappUserBundle:User', UserRepository);
                    
                

Repositories

As you defined your entity and its corresponding repository, you will now be able to work with it. In your code, for example in the controller, you will able to call the following integrated functions.

src UserBundle Controllers index.js

                    
                        var Controller = require('sonate-core').Controller,
                            View = new require('sonate-core').View(),
                            IndexController = new Controller(),
                            manager = require('sonate-core').database.manager,
                            UserRepository = manager.get('MyappUserBundle:User');

                        IndexController.index = function(req, res) {

                            // Create a new User
                            UserRepository.new({
                                email: 'email@email.com',
                                password: '***',
                                firstname: 'Firstname',
                                lastname: 'Lastname'
                            }).success(function(User) {
                                // Do something here!
                                // e.g. render a view
                                View.render(res, 'UserBundle:index:index', {
                                    User: User
                                });
                            });

                            // Find a user
                            UserRepository.find(id).success(function(User) {
                                // Do something here!
                            });

                            // Find one user by...
                            UserRepository.findOneBy(params, rsm).success(function(User) {...});

                            // Retrieve all users
                            UserRepository.findAll().success(function(users) {...});

                            // Find users by...
                            UserRepository.findBy(params, group, order, offset, limit).success(function(users) {...});

                            // Count users by...
                            UserRepository.count(params).success(function(nbOfUsers) {...});

                            // Max
                            UserRepository.max(column, params).success(function(max) {...});

                            // Min
                            UserRepository.min(column, params).success(function(min) {...});

                            // Sum
                            UserRepository.sum(column, params).success(function(sum) {...});

                        };

                        exports.IndexController = IndexController;
                    
                

Check Sequelizejs doc to find out more about the possibilities of this service. To access the Sequelize Entity, just call it from the repository.

                    
                        UserRepository.Entity. 'then call whatever Sequelize function you want...'
                    
                

Services

The services are here to have a clean code organisation and keep your controllers as short as possible.

The first thing you need to do is to declare your service

src UserBundle Resources config services.js

                    
                        var path = require('sonate-core').path;

                        exports.services = {

                            // Declare your service
                            UserService: require(path.__SERVICES__+'UserService').UserService

                        };
                    
                

Once declared, you need to define it. Here is the user service.

src UserBundle Services UserService.js

                    
                        var Parameters = require('sonate-core').parameters,
                            services = require('sonate-core').services,
                            manager = require('sonate-core').database.manager;

                        var UserService = {

                            // Declare global vars
                            // Set dependencies
                            encryption: services.get('encryption'),
                            UserRepository: manager.get('MyappUserBundle:User'),

                            // The register function
                            register: function(req, cb) {
                                var _this = this,
                                    data = req.body;

                                _this.UserRepository.new({
                                    email: data.email,
                                    password: _this.encryption.encrypt(Parameters.secret, data.password),
                                    firstname: data.firstname,
                                    lastname: data.lastname
                                }).success(function(User) {

                                    // Once the user is created we authenticate him.
                                    _this.auth(req, User, cb);
                                }).error(function(err) {

                                    // Throw an error
                                    console.log('error', err);
                                });
                            },

                            // Login function
                            login: function(req, success, error) {
                                var _this = this,
                                    data = req.body;

                                // Find one user by email
                                this.UserRepository.findOneBy({
                                    email: data.email
                                }).success(function(User) {
                                    // Once we found the user we will check if the input password is correct before authentication.
                                    if (data.password == _this.encryption.decrypt(Parameters.secret, User.password)) {
                                        _this.auth(req, User, success);
                                    }
                                    else error();
                                });
                            },

                            // Authentication function
                            auth: function(req, User, cb) {
                                req.session.user = User;

                                cb();
                            },

                            // Update function
                            update: function(req, res, cb) {
                                var _this = this,
                                    data = req.body;

                                if (data.password == data.confirm_password) {
                                    _this.UserRepository.find(req.session.user.id).success(function(User) {
                                        User.email = data.email;
                                        User.password = _this.encryption.encrypt(Parameters.secret, data.password);
                                        User.firstname = data.firstname;
                                        User.lastname = data.lastname;
                                        manager.persist(User)
                                        .flush(function() {
                                            req.session.user = User;
                                            cb();
                                        });
                                    });
                                } else res.redirect('/user/profile');
                            },

                            // Logout function
                            logout: function(req) {
                                req.session.user = null;
                            }

                        };

                        services.register('mysapp_user.manager', UserService);
                    
                

To access our newly created service in the controller we simply need to retrieve it and call our functions.

src UserBundle Controllers index.js

                    
                        var Controller = require('sonate-core').Controller,
                            View = new require('sonate-core').View(),
                            IndexController = new Controller();

                        IndexController.register = function(req, res) {
                            // Retrieve the registered user service
                            // Notice that the service manager is directly accessible as it a controller private variable
                            var UserManager = this.services.get('myapp_user.manager');

                            if (req.method == 'POST') {

                                // If the HTTP Request is POST then we call the register function of our service with a call back function as it is asynchronous.
                                UserManager.register(req, function() {
                                    View.redirect(res, '/user/profile');
                                });
                            } else {
                                View.render(res, 'UserBundle:index:index');
                            }
                        };

                        IndexController.login = function(req, res) {
                            var UserManager = this.services.get('myapp_user.manager');

                            if (req.method == 'POST') {

                                // Here we set 2 callback. One success callback if the user is properly login and one in case of error
                                UserManager.login(req, function() {
                                    View.redirect('/user/profile');
                                }, function() {
                                    View.render(res, 'UserBundle:index:login');
                                });
                            } else {
                                View.render(res, 'UserBundle:index:login');
                            }
                        };

                        IndexController.profile = function(req, res) {
                            View.render(res, 'UserBundle:index:profile', {

                                // Send the sessions user to the view
                                user: req.session.user
                            });
                        };

                        IndexController.update = function(req, res) {
                            var UserManager = this.services.get('myapp_user.manager');

                            if (req.method == "PUT") {
                                UserManager.update(req, res, function() {
                                    View.redirect(res, '/user/profile');
                                });
                            } else {
                                View.render(res, 'UserBundle:index:update', {
                                    user: req.session.user
                                });
                            }
                        };

                        IndexController.logout = function(req, res) {
                            var UserManager = this.services.get('myapp_user.manager');

                            // Logout the user and redirect to the home page
                            UserManager.logout(req);
                            View.redirect(res, '/');
                        };

                        exports.IndexController = IndexController;
                    
                

Mail

Mail in Sonate Framework is very simple as it is a service. It works like the others.

                    
                        IndexController.index = function(req, res) {

                            // Retrieve the mail service
                            var Mail = this.services.get('mail');

                            var options = {
                                from : // Not required. By default it will get the mail.from in the parameters.js
                                to : // to,
                                cc : // cc,
                                bcc : // bcc,
                                replyTo : // reply to,
                                subject : // subject,
                                text : // text mail,
                                html : // rich html mail body,
                                headers : // custom headers
                            };

                            Mail.send(options, function() {
                                // Success
                            }, function() {
                                // error
                            });

                        };
                    
                

Encryption

Just like the mail service, the encryption service is very easy to use and very secure as it works with the Stanford Javascript Crypto Library (SJCL).

                    
                        // Don't forget to include the parameters
                        var Parameters = require('sonate-core').parameters;

                        IndexController.index = function(req, res) {

                            // Retrieve the encryption service
                            var Encryption = this.services.get('encryption');

                            // Variable of type string to encrypt
                            var hello = "Hello!";

                            // Encrypt
                            var helloEncrypted = Encryption.encrypt(Parameters.secret, hello);

                            // Decrypt
                            var helloDecrypted = Encryption.decrypt(Parameters.secret, helloEncrypted);

                            // The variables helloDecrypted & hello are perfectly equal
                            if (hello === helloDecrypted) // true
                                // Do something!

                        };
                    
                

Real-time services

Here is the interesting part of the Sonate Framework. The real-time services build with Socket.io will enable instantaneous transactions between the client to the server to all users connected.

The first thing to do is to declare your event service.

src WelcomeBundle Resources config events.js

                    
                        var path = require('sonate-core').path;

                        exports.events = {

                            // Declare an event service
                            index: function(socket) {
                                require(path.__EVENTS__+'IndexEvents').IndexEvents(socket);
                            }

                        };
                    
                

Than create you service in the Events folder of the bundle.

src WelcomeBundle Events IndexEvents.js

                    
                        exports.IndexEvents = function(socket) {

                            // Send an event in real-time to the client
                            socket.emit('WelcomeBundle:index:message', 'Hello from the serve');

                            // Listen an event from the client and instantly execute the code
                            socket.on('WelcomeBundle:index:push', function(message) {

                                // Do something here!

                                // Than send back an event to the client when its done.
                                socket.emit('WelcomeBundle:index:message', message);
                            });

                        };
                    
                

Notice that you don't need to call anything from the controller as you're not trying to access code on a HTTP Request but in real-time. So all of the events services are listenning to the client.

Now that the server side is done, you need to make the client side javascript.

src WelcomeBundle Resources views layout.html

                    
                        <html>
                            <head>
                                <title>My first real-time page</title>
                                // meta tags
                                // stylesheets
                                // Simply include the Socket.io library in the client page
                                <script type="text/javascript" src="/socket.io/socket.io.js"></script>
                            </head>
                            <body>

                                {% block body %}{% endblock %}

                            </body>
                            <script type="text/javascript">
                                // Connect Socket.io to your server's socket
                                var socket = io.connect();

                                // Listen to the server event WelcomeBundle:index:message
                                socket.on('WelcomeBundle:index:message', function(message) {
                                    alert(message);
                                });

                                // Send an event to the server
                                socket.emit('WelcomeBundle:index:push', 'Hello server !');
                            </script>
                            {% block scripts %}{% endblock %}
                        </html>
                    
                

Real-time uploads

One of the best feature is the real-time upload. Your are not limited in size transfer and can monitor your upload progression percentage.

In your real-time event, you will need to implement a socket stream pipe.

src WelcomeBundle Events IndexEvents.js

                    
                        // Don't forget to require fs & socket.io-stream
                        var fs = require('fs'),
                            ss = require('socket.io-stream');

                        exports.IndexEvents = function(socket) {

                            // Create your socket stream listenner
                            ss(socket).on('WelcomeBundle:upload', function(stream, data) {

                                // Build your absolute & relative file path
                                var path = __dirname + '/../../..',
                                filename = '/web/uploads/' + Date.now() + '-' + Math.floor((Math.random()*1000)+1) + '.' + data.extension;

                                // Create a stream pipe to start uploading
                                stream.pipe(fs.createWriteStream(path + filename));

                                // Upload complete event
                                stream.on('end', function() {

                                    // Send to the client the uploaded filename
                                    socket.emit('WelcomeBundle:uploaded', filename);
                                });
                            });

                        };
                    
                

Than in your view, submit your file and display the upload percentage. Once uploaded, you will receive the event from the server with the uploaded file name.

src WelcomeBundle Resources views index index.html

                    
                        {% block body %}

                            <form id="form" action="#">
                                <input type="file" name="file_to_upload" required="required"/>
                                <input type="submit" value="Upload"/>
                            </form>
                            <div class="progress">
                                <div class="bar bar-success"></div>
                            </div>

                        {% endblock %}

                        {% block scripts %}

                            <script type="text/javascript" src="/web/js/libs/socket.io-stream.js"></script>
                            <script type="text/javascript">
                                
                                $('#form').on('submit', function(e) {
                                    e.preventDefault();
                                    e.stopPropagation();

                                    // Create a client stream
                                    var stream      = ss.createStream(),
                                        file        = e.files[0],
                                        $progress   = $('.progress > .bar');

                                    // Start the upload by emitting your file in the stream
                                    ss(socket).emit('WelcomeBundle:upload', stream, {extension:file.name.split('.').pop()});

                                    // Create a blob read stream to keep track of your file in the stream
                                    var blobStream = ss.createBlobReadStream(file),
                                        size = 0,
                                        percent = 0;

                                    // This event is triggerd every time a chunk of data has been received by the server
                                    blobStream.on('data', function(chunk) {
                                        size += chunk.length;
                                        percent = Math.floor(size / file.size * 100);
                                        $progress.css('width', percent+'%');
                                    });

                                    // Connect the blob stream to the previously created file stream
                                    blobStream.pipe(stream);

                                    // Event triggered when the file is completely uploaded
                                    socket.on('WelcomeBundle:uploaded', function(filename) {
                                        alert('Your file '+filename+' has successfully been uploaded.');
                                        socket.removeListener('WelcomeBundle:uploaded');
                                    });
                                });
                                
                            </script>

                        {% endblock %}