Persevere is a server-side JavaScript framework that we started several years ago to help build RESTful web applications with clean separation of concerns, encapsulation, and the consistency of JavaScript in both client and server. Recently, we had the opportunity to work on a project that used the full capabilities of Persevere, leveraging many of its features and leading to some improvements as well. We wanted to share some insights on how to best leverage some of the unique features of Persevere.

RESTful architecture with Persevere

Persevere is built for creating true RESTful, client-centric applications. Many frameworks provide tools for creating routes and endpoints that can be used in a RESTful manner, but Persevere provides a full RESTful infrastructure based on your data models. While the endpoints can be configured for any custom action, the default behavior of endpoints is to provide GET, PUT, POST, and DELETE functionality that maps to your data models, with full HTTP-compliant content negotiation, header handling, and even JavaScript Error to HTTP status code mapping. The easiest path, in Persevere, is always the most standards-compliant RESTful way.

perseverediagram

RESTful infrastructure

In this project, we had several different forms that could be used to create a resource, and a couple of them needed to include an input for uploading a file. Normally, when saving data to a server, we prefer to send and receive data in JSON, pulling data from the form and mapping it to an object that can be sent to the server. But a form with a file input can’t be read and encoded in JSON, as it needs to be directly submitted to the server with multipart/form-data, and the form must be targeted to a frame to avoid a full page refresh (unless you have the luxury of only targeting browsers that support FormData). Rather than creating another distinct endpoint for this action, just because of a different submission mechanism, HTTP/REST offers a better alternative: content negotiation. And Persevere makes it easy to leverage content negotiation.

Persevere’s middleware stack, Pintura, includes a content negotiation component, which automatically chooses the best registered content handler for the request’s data and the response data, based on the standard HTTP content negotiation rules. By default, Pintura includes a multipart/form-data decoder, so the form submitted with a file input (in multipart format) can be automatically parsed and delivered to the application code as a serialized object. This behaves exactly like the JSON decoding handler (except one of the properties points to the uploaded file). We don’t have to do anything within our application code to separately handle both the multipart and JSON data, it is built in.

Our HTTP response to this form submission request requires a little bit more effort. To be able to read the response from the server, Dojo looks for a textarea element (with JSON inside) in the form response in the target frame. This isn’t a standardized content type or format, so we need to register our own content handler. Fortunately, this is not difficult, we simply create a new Media instance (using the pintura/Media module), specify the text/html type, give a high quality/priority (close to 1, as 1 is the highest quality) and create a serialize method. This will auto-register the handler. In the serialize method, we send a simple HTML document with a textarea element and stream the JSON response within the textarea element:

var media = require('pintura/media').Media,
	when = require('promised-io/promise').when,
	StreamingSerializer = require('pintura/media/json').StreamingSerializer,
	toJSON = new StreamingSerializer(JSON.stringify);

media({
	mediaType: 'text/html',
	getQuality: function () {
		// give it a high quality since we can't control the Accept
		// header on a form submission
		return 0.95;
	},
	serialize: function (object, mediaParams, request, response) {
		return {
			forEach: function (write) {
				// write a simple HTML document
				write('


');
				});
			}
		};
	}
});

Once we have this module loaded, it will be used to serialize any request with text/html as the content-type specified in the Accept header. This is included by the browser for all form submission requests. Then for XHR, we can explicitly specify an Accept: application/json header for all standard JSON interactions.

With Persevere’s content negotiation infrastructure, we can add a new content handler, and our application code can remain unchanged, handling both JSON, multipart/form-data, and textarea embedded responses without needing to worry about the different formats that are used across the wire. The RESTful paradigm of cleanly distinguishing resources and their logic from format representations is preserved, and the quality and manageability of our RESTful infrastructure is preserved with little effort.

Dojo concepts on the server

In many ways, Persevere brings the concepts of Dojo to the server, providing implementations of some of the same interfaces for server-side needs. One of the key interfaces that is shared with Persevere is the object store API. Not only is this central for Dojo, it is equally foundational for Persevere applications. In Dojo, object stores are often gateways to HTTP-based interaction with the server. On the server, in Persevere, they are often adapters to databases. They also share similar implementations for memory stores. With Dojo and Persevere, we can use a consistent API on both the client and server.

In our recent project, we implemented our object store for interacting with Amazon’s S3 storage. Building on the knox library, implementing the store interface for S3 interaction was relatively straightforward, and the common interface made it familiar and simple to use the S3 store throughout the application. Dojo and Persevere also offer complementary concepts. Dojo’s JsonRest store and dojo-smore’s RequestMemory store works easily with Persevere stores, using the same conventions for RESTful interaction, sorting, and paging. We were able to use the RequestMemory store on the client-side to retrieve data from Persevere for display in our client-side components, with a consistent store interface all the way from the S3 back-end, through the application logic, across the wire, through the RequestMemory store, to the final user-facing components.

Promises

Persevere uses essentially the same promise system and API as Dojo. Many of the ideas for promises were explored and matured within both Dojo and Persevere. Dojo developers have long enjoyed the encapsulation of eventuality that promises provide, and the same benefits are available on the server-side. However, on our recent project, I was reminded that typical server-side JavaScript built on the NodeJS platform makes far more use of asynchronous actions, and consequently promises (if built properly). Servers can easily use vastly more asynchronous code-paths than a typical client-side application. Between database interactions, file I/O, HTTP requests, child processes (which were heavily used), and more, server-side JavaScript can quickly become an overwhelming web of callbacks if not approached properly. Promises are a powerful tool in dealing with the complexity of composing various asynchronous actions and managing them in a coherent and encapsulated way, with reasonable flow and error handling. Most client-side modules only begin to venture into the full experience of the complex combinations of serial and parallel actions that can be commonplace on a NodeJS server, and can be smoothly handled by promises.

Persevere uses promises throughout the infrastructure. Just like Dojo, object stores are promise-driven. The entire JSGI-based middleware stack is built on promises, and Persevere provides a full I/O library using promises to model the completion of file interactions. Promises originating from object stores can easily propagate through to the middleware stack with straightforward chaining. These can easily be chained and combined with promises originating from File I/O or child process interaction. Persevere brings the capabilities of Dojo’s promise to full effect on the server. Leveraging promises on the server-side is extremely valuable, as well as promise tools like the all() that can combine promises to wait for parallel actions to complete.

More Improvements

Building on Persevere gave us the chance to also add some new improvements. You can now use the rc package to define settings. This means you can now set configuration information through command line arguments, environmental variables, and use JSON or INI configuration files in various locations.

We have also separated two of the more important store implementations into separate projects. We now have a distinct MongoDB store package for connecting MongoDB databases with NodeJS, and a MySQL store for using MySQL with NodeJS as well. These packages now explicitly define their dependencies, (since they are separate from perstore) for easier installation with NPM. These stores still offer the same functionality, seamlessly providing an object store using the different storage systems.

Persevere continues to improve and mature. And as JSON-based RESTful server interfaces, as well as client-centric applications become more ubiquitous, Persevere is a great fit, particularly for those who enjoy the consistency of JavaScript-based promises and object stores, from client to server.

Learning more

If you need assistance beyond the Persevere documentation and tutorials and the community mailing list, we also cover Persevere as part of our JavaScript support. If you’d like more information about having SitePen assist your organization with implementing a robust web application architecture using server-side JavaScript with Node.js and Persevere, please let us know.