Featured Work


Lightweight SPA framework based on Convention over Configuration.

Available on GitHub

Annotated source code


Best Practices

As its name implies, Dry.js is based around the idea of applying the DRY principle as extensively as possible. It does so by following a Convention Over Configuration approach.


Initial page load is one of the main concerns of Dry.js. The library weights less than 8kb minified (3kb gzipped) which makes it viable for any sort of web application.

Easily replaceable components

Dry.js is intended to be a standard way to join external components and libraries. All of its components are designed so that they can be easily replaced.

Easy to learn, easy to use

No one likes learning a new front-end framework every week, so that's why Dry.js was specifically designed to be super easy to use. Complexity is the first sign of bad architecture, and your developers will thank you for it.

Easy to change

This framework's code is so brief and simple that you can easily replace any of the framework's core components with external libraries. Run the tests afterwards and you'll know if everything works as expected.


Dry.js is designed to work on small to large projects, just like an umbrella can keep you dry if it rains or protect you from the sun if you are on the desert.


Dry.js was inspired by what I consider to be the best features of the most well-known single page application frameworks:

From Backbone.js

Provides only the minimum set of components necessary for web application development.

From Ember.js

Follows an MVC, convention over configuration approach (although much simpler).

From Angular.js

Comes with all you need right out of the box, alghough these features can be easily replaced by other external components.

From React/Flux

Integrates an opinionated set of guidelines for the application's architecture and data flow.

Mamun Srizon

Getting Started

   <title>Getting started</title>
   <h1>Hello world</h1>

   <!-- Include dry.min.js here -->
   <script src="scripts/dry.min.js"></script>

Setting up Dry.js is extremely easy. You only need to include the script anywhere on the page, and you are all set.

You can download the minitied and development versions through any of these options.

  • Through the Download link at the top of the page.
  • Install with Bower: bower install dry-js
  • Install with npm: npm install dry-js



A website is divided in apps. Their goal is to provide structure to the code, and can be seen as modules. Each app contains its own controllers, views and models, and handles a specific set of routes.

// Create new app
var app1 = dry.app('app1');
// Start the app


A controller contains methods. Each method handles a specific route or event, returning a view instance if the result of the method's execution triggers UI changes. In this sense, Dry.js controllers are very similar to Rails or ASP.NET MVC.

var app1 = dry.app('app1');

// Handles a list of products
app1.controller('products', {
    // Triggered when navigating to /products/list
    'list': function() {
        return new dry.View('ProductList')


The router is responsible for directing page navigation actions to controllers. As the previous example hints, you do not need to specify routes anywhere. They are automatically generated from controller names and methods.

var app1 = dry.app('app1');

// Home page
app1.controller('default', {
    // Triggered on base url (root)
    'default': function() {
        return new dry.View('HomePage')

// Handles a list of products
app1.controller('products', {
    // Triggered when navigating to /products/:id
    ':id': function(id) {
        return new dry.View('ProductDetails')
    // Triggered when navigating to /products/list
    'list': function() {
        return new dry.View('ProductList')


In a typical fashion, models concentrate the responsibility of handling data structure, storage and server communication. They are injected into views through controller actions, and come with a convenient API for handling Ajax communication. Expanding on the previous example:

var app1 = dry.app('app1');

// Product model definition
app1.model('Product', {
    attributes: {
        id: 1 // default
    getAll: 'GET https://someUrl/products'
    newProduct: 'POST https://someUrl/products/new',
    updateProduct: 'PUT https://someUrl/products/{id}',
    deleteProduct: 'DELETE https://someUrl/{id}'

// Handles a list of products
app1.controller('products', {
    'list': function() {
        // Creates a new model instance and calls the getAll method
        var allProducts = app1.model('Product');
        return allProducts
            .getAll() // Promises are built-in
            .then(function (data) {
                // Pass the model to the view
                new dry.View('ProductList', {model: allProducts});


Views contain presentation logic. Each view contains a model instance which stores the data that is rendered on the template. They are also responsible for handling events, and do so by calling methods from the controller that created the view.

var app1 = dry.app('app1');

// Product model definition

app1.view('ProductList', {
    events: {
        // When the button with class .btn-new' is pressed,
        // call the 'new' method in the controller
        'click .btn-new': 'new'

// Handles a list of products
app1.controller('products', {
    'new': function() {
        // Creates a new model instance
        var product = app1.model('Product');
        // Pass the model to the view
        return app1.view('ProductList', {model: product});


Templates can be either strings or functinons which construct HTML code from the view data. Dry.js automatically finds template definitions in the dom looking for a script with a data-dry attribute, and then renders it to a DOM element with a data-dry attribute of the same value.

The convention here is very simple: by default, once a user navigates to a route (say www.someUrl.com/main/hello), then the main controller executes the hello method, which will create a new instance of the main/hello view.

Notice that the view instance is created on the fly, and since the only parameter specified when creating the view is its name, it will simply follow the default behavior for views. More specifically, it look for a script with a data-dry attribute equal to its name (the template) and render it into the first non-script DOM element with the same attribute value. Please read the Default behavior and best practices section for additional info.

var app1 = dry.app('app1');

// Home page
app1.controller('main', {
    'hello': function() {
        return new dry.View('main/hello');

<!-- Template -->
<script data-dry="main/hello" type="text/template">
    <% for(var i=0; i<10; i++) { %>
        Lorem ipsum
    <% } %>

<!-- Template will be rendered here -->
<div data-dry="main/hello"></div>


Filters are a simple and semantic way to reuse code inside your application (again, DRY principle). They check if a condition is fulfilled and execute a given action if it is. They can seve as annotations and make controller logic much easier to read.

Note: filters are not required, so you can choose not to use them if you find them irrelevant or confusing.

var app1 = dry.app('app1');

function isLoggedIn() {
    // some logic...

function redirectToHome() {

app1.filter('IsLoggedIn', isLoggedIn, redirectToHome);

// Home page
app1.controller('main', {
    'hello': function() {

          // Continue controller logic...


Dry.js also contains several utility functions which you can leverage in your own code.

Tip: You can open your browser's dev tools and check them out this page :)

Type Checking

// Check if the given parameter is an array
dry.isArray([1,2,3]) => true

// Check if the given parameter is an object
dry.isObject({a:1, b:2}) => true

// Check if the given parameter is strictly an object
dry.isStrictlyObject({'a':1, 'b':2}) => true
dry.isStrictlyObject([1,2,3]) => false

// Check if the given parameter is boolean
dry.isBoolean(true) => true

// Check if the given parameter is a string
dry.isString('Hi there') => true

// Check if the given parameter is a function
dry.isFunction(function(){}) => true

// Check if the given parameter is undefined
dry.isUndefined(undefined) => true

// Check if the given parameter is numeric
dry.isNumeric(1) => true

Object management

// Returns an array with the property names for the given object
dry.keys({'a':1, 'b': 2, 'c': 3}) => ['a', 'b', 'c'];

// Concise and efficient forEach implementation
var arr = [10, 20, 30];
dry.each(arr, function(number) {
// Logs 10 20 30

var context = {
    valueMessage: 'The value is',
    indexMessage: 'at index'
dry.each(arr, function(num, index) {
    console.log(this.valueMessage, num, this.indexMessage, index);
}, context);
// Logs:
// The number is 10 at index 0
// The number is 20 at index 1
// The number is 30 at index 2

Ajax communication

// jQuery-like ajax implementation
    type: 'GET',
    url: 'https://api.github.com/users/diegocard',
    timeout: 5000,
    success: function(data){
    error: function() {

// There are also default values for most parameters
function ajaxCall(someUrl) {
    return dry.ajax({
        url: someUrl
    }) // Returns a promise

DOM Management

// jQuery-like selectors

// jQuery-like html function

// jQuery-like event handlers
var clickHandler = function() {

// Activate click event
dry.$('body').on('click', clickHandler);

// Trigger the click event
dry.$('body').trigger('click', /* You can pass data here */);

// Deactivate click event
dry.$('body').off('click', clickHandler);

A+ Promises

function randomOutcome(number) {
    var deferred = dry.deferred();
        if (Math.random() > number) {
        } else {
    }, 1000);
    return deferred.promise;

    console.log(outcome, 'than 0.5');
    console.log(outcome, 'than 0.5');

And other shortcuts

  • dry.getJSON(url, [success], [error]);
  • dry.get(url, [success], [error]);
  • dry.post(url, data, [success], [error])
  • dry.put(url, data, [success], [error])
  • dry.delete(url, data, [success], [error])
  • dry.jsonp(url, callback)