Application Development with Rapid Application Development Framework AllcountJS

The idea of Rapid Application Development (RAD) was born as a response to traditional waterfall development models. Many variations of RAD exist; for example, Agile development and the Rational Unified Process. However, all such models have one thing in common: they aim to yield maximum business value with minimal development time through prototyping and iterative development. To accomplish this, the Rapid Application Development model relies on tools that ease the process. In this article, we shall explore one such tool, and how it can be used to focus on business value and optimization of the development process.

AllcountJS is an emerging open source framework built with rapid application development in mind. It is based on the idea of declarative application development using JSON-like configuration code that describes the structure and behavior of the application. The framework has been built on top of Node.js, Express, MongoDB and relies heavily on AngularJS and Twitter Bootstrap. Although it is reliant on declarative patterns, the framework still allows further customization through direct access to API where needed.

Why AllcountJS as Your RAD Framework?

According to Wikipedia, there are at least one hundred tools that promise rapid application development, but this raises the question: how rapid is “rapid.” Do these tools allow a particular data-centric application to be developed in a few hours? Or, perhaps, it is “rapid” if the application can be developed in a few days or a few weeks. Some of these tools even claim that a few minutes is all it takes to produce a working application. However, it is unlikely that you could build a useful application in under five minutes and still claim to have satisfied every business need. AllcountJS doesn’t claim to be such a tool; what AllcountJS offers is a way to prototype an idea in a short period of time.

With AllcountJS framework, it is possible to build an application with a themeable auto-generated user interface, user management features, RESTful API and a handful of other features with minimal effort and time. It is possible to use AllcountJS for a wide variety of use cases, but it best suits applications where you have different collections of objects with different views for them. Typically, business applications are a good fit for this model.

AllcountJS has been used to build allcountjs.com, plus a project tracker for it. It is worth noting that allcountjs.com is a customized AllcountJS application, and that AllcountJS allows both static and dynamic views to be combined with little hassle. It even allows dynamically loaded parts to be inserted into static content. For example, AllcountJS manages a collection of demo application templates. There is a demo widget on the main page of allcountjs.com that loads a random application template from that collection. A handful of other sample applications are available in the gallery at allcountjs.com.

Getting Started

To demonstrate some of the capabilities of RAD framework AllcountJS, we will create a simple application for Toptal, which we will call Toptal Community. If you follow our blog you may already know that a similar application was built using Hoodie as part of one of our earlier blog posts. This application will allow community members to sign up, create events and apply to attend them.

In order to set up the environment, you should install Node.js, MongoDB and Git. Then, install AllcountJS CLI by invoking an “npm install” command and perform project init:

Although AllcountJS works with Git repositories, for sake of simplicity we will not use it in this tutorial. To run the Toptal Community application, all we have to do is invoke AllcountJS CLI run command in the toptal-community-allcount directory.

allcountjs run

It is worth noting that MongoDB should be running when this command is executed. If all goes well, the application should be up and running at http://localhost:9080.

To login please use the username “admin” and password “admin”.

Less Than 100 Lines

You may have noticed that the application defined in main.js took only 91 lines of code. These lines include the declaration of all the behaviors that you may observe when you navigate to http://localhost:9080. So, what exactly is happening, under the hood? Let us take a closer look at each aspect of the application, and see how the code relates to them.

Sign in & Sign up

The first page you see after opening the application is a sign in. This doubles as a sign up page, assuming that the checkbox - labelled “Sign Up” - is checked before submitting the form.

This page is shown because the main.js file declares that only authenticated users may use this application. Moreover, it enables the ability for users to sign up from this page. The following two lines are all that was necessary for this:

A.app({
...,
onlyAuthenticated: true,
allowSignUp: true,
...
})

Welcome Page

After signing in, you’ll be redirected to a welcome page with an application menu. This portion of the application is generated automatically, based on the menu items defined under the “menuItems” key.

Along with a couple of other relevant configurations, the menu is defined in the main.js file as follows:

AllcountJS uses Font Awesome icons, so all icon names referenced in the configuration are mapped to Font Awesome icon names.

Browsing & Editing Events

After clicking on “Events” from the menu, you’ll be taken to the Events view shown in the screenshot below. It is a standard AllcountJS view that provides some generic CRUD functionalities on the corresponding entities. Here, you may search for events, create new events and edit or delete existing ones. There are two modes of this CRUD interface: list and form. This portion of the application is configured through the following few lines of JavaScript code.

This example shows how entity descriptions are configured in AllcountJS. Notice how we are using a function to define the entities; every property of AllcountJS configuration can be a function. These functions can request dependencies to be resolved through its argument names. Before the function is called, appropriate dependencies are injected. Here, “Fields” is one of the AllcountJS configuration APIs used to describe entity fields. The property “Entities” contains name-value pairs where the name is an entity-type identifier and value is its description. An entity-type for events is described, in this example, where the title is “Events.” Other configurations, such as default-sort-ordering, reference name, and the like, may also be defined here. Default-sort-order is defined through an array of field names and directions, while the reference name is defined through a string (read more here).

This particular entity-type has been defined as having four fields: “eventName,” “date,” “time” and “appliedUsers,” the first three of which are persisted in the database. These fields are mandatory, as indicated by the use of “required().” Values in these fields with such rules are validated before the form is submitted on the front-end as shown in the screenshot below. AllcountJS combines both client-side and server-side validations to provide the best user experience. The fourth field is a relationship that bears a list of users who have applied to attend the event. Naturally, this field is not persisted in the database, and is populated by selecting only those AppliedUser entities relevant to the event.

Applying to Attend Events

When a user selects a particular event, the toolbar shows a button labelled “Apply.” Clicking on it adds the event to the user’s schedule. In AllcountJS, actions similar to this can be configured by simply declaring them in the configuration:

The property “actions” of any entity type takes an array of objects that describe the behavior of each custom action. Each object has an “id” property which defines a unique identifier for the action, the property “name” defines the display name and the property “actionTarget” is used to define the action context. Setting “actionTarget” to “single-item” indicates that the action should be performed with a particular event. A function defined under the property “perform” is the logic executed when this action is performed, typically when the user clicks on the corresponding button.

Dependencies may be requested by this function. For instance, in this example the function depends on “User,” “Actions” and “Crud.” When an action occurs, a reference to the user, invoking this action, can be obtained by requiring the “User” dependency. The “Crud” dependency, which allows the manipulation of database state for these entities, is also requested here. The two methods that return an instance of Crud object are: The method “actionContextCrud()” - returns CRUD for “Event” entity-type since the action “Apply” belongs to it, while the method “crudForEntityType()” - returns CRUD for any entity type identified by its type ID.

The implementation of the action begins by checking if this event is already scheduled for the user, and if not, it creates one. If it is already scheduled, a dialog box is shown by returning the value from calling “Actions.modalResult()”. Besides showing a modal, an action may perform different types of operations in a similar way, such as “navigate to view,” “refresh view,” “show dialog,” and so on.

User Schedule of Applied Events

After successfully applying to an event, the browser is redirected to the “My Events” view, which shows a list of events the user has applied to. The view is defined by the following configuration:

In this case, we are using a new configuration property, “filtering.” As with our earlier example, this function also relies on the “User” dependency. If the function returns an object, it is treated as a MongoDB query; the query filters the collection for events that belong only to the current user.

Another interesting property is “Views.” “View” is a regular entity-type, but it’s MongoDB collection is the same as for parent entity-type. This makes it possible to create visually different views for the same data in the database. In fact, we used this feature to create two different views for “UserEvent:” “MyEvent” and “AppliedUser.” Since the prototype of the sub-views is set to the parent entity type, properties that are not overridden are “inherited” from the parent type.

Listing Event Attendees

After applying to an event, other users may see a list of all the users planning to attend. This is generated as a result of the following configuration elements in main.js:

“AppliedUser” is a read-only view for a “MyEvent” entity-type. This read-only permission is enforced by setting an empty array to the “Write” property of the permissions object. Also, as the “Read” permission isn’t defined, by default, reading is permitted for all users.

Extending Default Implementations

The typical backdraw of RAD frameworks is the lack of flexibility. Once you’ve built your app and you need to customize it, you may encounter significant obstacles. AllcountJS is developed with extensibility in mind and allows replacement of every building block inside.

To achieve that AllcountJS uses it’s own Dependency Injection (DI) implementation. DI allows the developer to override the default behaviors of the framework through extension points, and, at the same time, allows it through the reuse of existing implementations. Many aspects of RAD framework extension are described in the documentations. In this section, we will explore how we may extend two of the many components in the framework, server side logic and views.

Continuing with our Toptal Community example, let us integrate an external data source to aggregate event data. Let’s imagine that there are Toptal Blog posts discussing plans for events the day before each event. With Node.js, it should be be possible to parse the blog’s RSS feed and extract such data. In order to do this, we will need some extra npm dependencies, such as “request,” “xml2js” (to load Toptal Blog RSS feed), “q” (to implement promises) and “moment” (to parse dates). These dependencies can be installed by invoking the following set of commands:

After that, simply reference it from the “Event” entity in main.js as “customView: “events”.” Run your app and you should see a card-based interface instead of the default tabular one.

Conclusion

Nowadays, the development flow of web applications is similar across many web technologies, where some operations are repeated over and over again. Is it really worth it? Maybe, it is time to rethink the way your web applications are developed?

AllcountJS provides an alternative approach to rapid application development frameworks; you start by creating a skeleton for the application by defining entity descriptions, and then add views and behavior customizations around it. As you can see, with AllcountJS, we created a simple, yet fully functional application, in less than a hundred lines of code. Maybe, it doesn’t meet all production requirements, but it is customizable. All of this makes AllcountJS a good tool for rapidly bootstrapping web applications.

My experience with very declarative frameworks (see: lots of configuration) has never been amazing. Take Ext JS, Sencha Touch, or even Grunt. Often a steep learning curve, and the troubles begin when you have to do something different from the standard - but still play well with the internal architecture of the framework. For me it's also a matter of personal coding style: I do prefer writing code than configuring existing components, and I do prefer crafting a bespoke application tailored on the user needs. But in the end, I agree that there are always very valid use cases for these frameworks: usually when they are not part of the core business.

Pavel Tiunov

Valentino thank you for sharing your experience! Agree with you when winning in development speed usually losing in flexibility. AllcountJS is initially designed to overcome such problems by allowing customize almost every part of it using DI. It's where code writing comes into play to create application that satisfies user needs.

Gavin Engel

This seems similar to dcl.js, although I think allcount.js seems easier to use. What other node.js frameworks are similar to allcount.js? By the way, I think it RAD syntax that is further shrunk down to look something similar to SCSS would be great.

beerfan

This sort of rad tools prove good for building mock ups . It's good for designer who want to build configuration based pages . I don't think this will be useful in building enterprise apps .

Hello Orlando!
Yes, you can. Just create multiple *.js files in app config directory. Each JS file should have own `A.app()` call.

garduino

Hi Pavel:
I'm trying to follow this interesting tutorial but can't start the app, I get:
Failed to fetch "toptal-community-repo". Trying to use as regular directory.
Application server for "toptal-community-repo" listening on port 9080
/Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/base.js:246
throw message;
^
TypeError: Cannot read property 'length' of undefined
Any hint to solve this issue will be appreciated.
Thanks.

garduino

My first attempt was in a Mac OSX, now I tried in a Windows box with no luck.
Even when the login screen get displayed, after type us/pw I get the same error (pic attached).

Pavel Tiunov

Hello garduino!
Could you please share your stack trace or full console log?

garduino

Sure!
In the first attempt in each machine it started and I get the error after typing us/pw. But in the next attempts the app refuses to start with the following errors:
node_modules/.bin/allcountjs --git toptal-community-repo --db mongodb://localhost:27017/toptal-community
##############################################################
#
# !!! MONGOOSE WARNING !!!
#
# This is an UNSTABLE release of Mongoose.
# Unstable releases are available for preview/testing only.
# DO NOT run this in production.
#
##############################################################
Failed to fetch "toptal-community-repo". Trying to use as regular directory.
Application server for "toptal-community-repo" listening on port 9080
/Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/base.js:246
throw message;
^
TypeError: Cannot read property 'length' of undefined
at processResults (/Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1581:31)
at /Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1619:20
at /Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1157:7
at /Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/db.js:1890:9
at Server.Base._callHandler (/Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/base.js:448:41)
at /Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:481:18
at MongoReply.parseBody (/Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/responses/mongo_reply.js:68:5)
at null.<anonymous> (/Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:439:20)
at emit (events.js:107:17)
at null.<anonymous> (/Users/Shared/gsa/Dev/CodeRepository/toptal-community-allcount/node_modules/allcountjs/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/connection_pool.js:201:13)
Let me know if I should send any other data.
Thanks.

Pavel Tiunov

Very interesting.
Looks like it's some kind of that bug: https://github.com/Automattic/mongoose/issues/2684
What MongoDB version do you use and what mongoose version has been selected by npm while install?

garduino

The MongoDB version is:
C:\MongoDB\Server\3.0\bin>mongod --version
db version v3.0.3
git version: b40106b36eecd1b4407eb1ad1af6bc60593c6
OpenSSL version: OpenSSL 1.0.1m-fips 19 Mar 2015
but I do not know how to find the mongoose version installed by npm (I'm not familiar with Mongo, sorry) but I imagine that should be the latest.

garduino

Anyway I tried "npm install mongoose" and after the installation I get the same error trying to start the toptal sample application.

Pavel Tiunov

Yep. It's a mongoose 3.9 compatibility issue with MongoDB 3.0. I fixed version to minor instead of major (3.8) and it's fixed. Released 1.10.5. Please run `npm update` from your project dir (toptal-community-allcount) and try run application again.
Thanks for that catch!

garduino

Excellent, it works now and I can continue the tutorial. Thanks you for the help1
Two more questions:
1. What you fixed to make it work?
2. What npm update does to help fix it?
As you already imagined I'm not familiar with node-npm-etc, just learning.
Thanks again.

Pavel Tiunov

1. Changed mongoose version constraint from "^3.8.21" to "~3.8.21" in package.json. It means that it could select only patch version after 21 and not a 9 minor where compatibility bug is.
2. npm update just updates installed dependencies where newer version could be loaded that matches version constraints.

garduino

Thanks you very much!

Pavel Tiunov

You're welcome!

David

This is fantastic!!!!
I am getting an error with the import feature.
C:\n\nodeprj\toptal-community-allcount\node_modules\allcountjs\node_modules\q\q.
js:126
throw e;
^
Error: toptal-community.js:1
var request = require('request');
^
ReferenceError: require is not defined
at toptal-community.js:1:15
The app starts and works before I add the toptal-community.js file. The file to add the import feature.
I am new to js frameworks.
Can someone help?

David

I got this resolved with Pavel's help.
See:
https://groups.google.com/d/msg/allcountjs/9C5A2qrrdHQ/ZTzwMloBkIUJ
and
http://davidgleba.blogspot.ca/2015/07/trying-out-allcountjs-javascript-web.html

Axle Tom

Hi Pavel,
This is a great article. You're on the right track but you will likely hear all sorts of excuses about why Allcount.js won't work in this environment or that environment.
Just wanted to offer some insights and give some perspective about why there will be some resistance to this way of building apps and why this is generally a mistake.
The Why?
People when confronted with new technology will apply their past experience's and personal biases to the new technology, in this case Allcount.js. This is not a good way to think about things.
New technologies need new ways of thinking and doing. Why? because new technologies remove previous constraints. This in turn creates a new operating landscape. Past models of thinking and doing things can't be applied because the landscape has shifted.
Looking forward into the future about how application development work will evolve and you can be fairly certain that it will become increasingly commoditised (i.e. the same) in a majority of areas. The research is clear on this general trend. It's only a matter of when, not if. Allcount.js is just one of those pieces in the puzzle pushing further toward that future.
To get a good perspective on things you need a framework in which to understand how this will generally play out moving forward. So using research from Simon Wardely, this is how things will likely progress applied as applied to building apps:
1. Validate
- Write everything from scratch to build your app. Most of the work is focused on validating that things work as expected.
2. Build
- Write with Frameworks / libraries to build your app. Most work is focused on building the app using the framework / libraries. This is where most of the industry is.
3. Configure
- Configure some parameters to build your app. Most work is focused on the configuration of the app. This is where Allcount.js is.
4. Use (Plug & Play)
- Use an app, a feature or a function to build your app. Most of the work is focused on looking for reuseable components and linking them up. So think an App Store of reusable components for Allcount.js. If the Allcount.js community focuses toward this eventual end goal they could revolutionise how apps get built.
It's important to note that not all apps will be built this way. There will be certain niches where past models will still be applicable but generally speaking, the majority will trend towards this over time.
Hope that sheds some light and perspective on things.