Last month I published an article onwriting RESTful web services in Python, in which I developed a small web service.
Today I'm putting my "front-end" hat to show you how to write a Javascript client application that uses the Python service.
The source code for this tutorial is available on myREST-tutorial project on github. The incremental versions are tagged and linked in the appropriate sections of the article to save you from having to type or copy/paste the code yourself.
Download the initial version:zip |github
This version includes just the REST server. To execute it under Python's development web server you have to run the following command:
./rest-server.py
python rest-server.py
The development server will be listening for requests on port 5000 of the local host, so you'll access it ashttp://localhost:5000
.
In theaforementioned article I developed a simple web service that maintains a to-do list. The REST API endpoints for this service are:
HTTP Method | URI | Action |
---|---|---|
GET | http://[hostname]/todo/api/v1.0/tasks | Retrieve list of tasks |
GET | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Retrieve a task |
POST | http://[hostname]/todo/api/v1.0/tasks | Create a new task |
PUT | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Update an existing task |
DELETE | http://[hostname]/todo/api/v1.0/tasks/[task_id] | Delete a task |
All the endpoints in this service are secured byHTTP basic authentication.
The only resource exposed by this service is a "task", which is composed of the following data fields:
The REST server article shows how to place calls into this server using a command line utility calledcurl
. Please see that article for details on this if you are interested in learning about the server side.
We will not discuss any more server related matters today. Are you ready to cross to the client-side? Let's go!
We will develop a client application that will run on web browsers, so we need to decide what tools and/or frameworks we will use.
For the base stack we don't really have much of a choice: The layout will be done in HTML, styling will be done in CSS and scripting in Javascript. No surprises here. While there are other choices, these guarantee that most modern browsers will run our application.
But these technologies alone would make for a rough development experience. For example, while Javascript and CSS work in all browsers, implementations differ, many times in subtle or obscure ways. There are three areas in which we would benefit from higher level cross-browser frameworks:
Let's review each and evaluate what options there are.
We don't really have the patience nor the interest to test several browsers to make sure our HTML, CSS and Javascript works. There are a few frameworks that provide already tested CSS styles and Javascript functions to build websites with a modern look and user interface.
We will useTwitter Bootstrap, the most popular CSS/Javascript layout framework.
Our client application running inside the web browser will need to issue requests to the REST web server. The browser's Javascript interpreter provides an API for this calledXMLHttpRequest, but the actual implementation varies from browser to browser, so we would need to write browser specific code if we wanted to code against this API directly.
Lucky for us there are several cross-browser Javascript frameworks that hide these little differences and provide a uniform API. Once again we will pick the leading framework in this category,jQuery, which not only provides a uniform Ajax API but a large number of cross-browser helper functions.
Finally, our client application will need to generate dynamic content that will be inserted into an existing HTML document, and also update this content as a response to the actions issued by the user. We could use jQuery for all this, but jQuery's support in this area is pretty basic.
Many application frameworks adopt a pattern calledModel View Controller to write maintainable applications that separate data from behavior. In MVC frameworks models store the data, views render the data and controllers update views and models according to user actions. The separation between data, presentation and behavior is very clear. There are some variations of MVC, like MVP (Model View Presenter) or MVVM (Model View ViewModel) that are popular as well. All these patterns are usually referred together as MV* frameworks.
MV* frameworks in the client side is a very hot topic these days, there are several projects that compete in this area without a clear winner. Here are the ones that I have evaluated:
AngularJS and Ember.js are the ones with the most features, in particular both have two-way data binding, which allows you to associate Javascript variables to elements in the HTML page so that when one changes the other updates automatically. They both look pretty good, but the learning curve is steep, and their documentation is pretty bad.
Backbone is the oldest framework of the four. It has no automatic data binding, instead you have to set up event handlers and write the code to perform the updates inside them.
Knockout was a surprise. This is a framework that is smaller than the other three, its main feature is two-way data binding. The documentation is excellent, very detailed and complete. There is a fun interactive tutorial followed by reference documentation and several examples, all integrated into an easy to navigate site.
Based on the above analysis, I've decided to giveKnockout a chance.
Let's begin by creating the main page skeleton:
<!DOCTYPE html><html><head><title>ToDo API Client Demo</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet"><script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.js"></script><script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script><script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js"></script></head><body> <div> <div> <a href="#">ToDo API Client Demo</a> </div> </div> <div> Main content here! </div> <script type="text/javascript"> // application code here! </script></body></html>
This is a pretty standard HTML5 document.
Themeta name="viewport"
comes from Bootstrap. It makes the page scale according to the browser dimensions, be it a browser on a desktop PC or one in a smartphone. What follows is the Bootstrap CSS file, which we import from a CDN (content delivery network) so that we don't have to host it ourselves. Next come the Javascript files for jQuery, Bootstrap and Knockout, also imported from CDNs.
In the body of the page we first have a top bar with the application title, created using Bootstrap's CSS styles. Then we have the main content area, where we will insert the application data. And finally, we will have our application code at the bottom, keeping in mind that for larger projects the application code should likely go into one or more independent Javascript source files.
Download the project at this stage:zip |github
To see how the page looks start the Python server and then navigate tohttp://localhost:5000/index.html
in your browser.
Using Bootstrap styles we can create a mock up of the content area of our application:
Task | Options | |
Done | Task title Task description | |
In Progress | Task title Task description |
The HTML code that achieves the above look is:
<table> <tr><td></td><td><b>Task</b></td><td><b>Options</b></td></tr> <tr> <td> <span>Done</span> </td> <td><p><b>Task title</b></p><p>Task description</p></td> <td> <button>Edit</button> <button>Delete</button> <span><button>Mark In Progress</button></span> </td> </tr> <tr> <td> <span>In Progress</span> </td> <td><p><b>Task title</b></p><p>Task description</p></td> <td> <button>Edit</button> <button>Delete</button> <span><button>Mark Done</button></span> </td> </tr></table><button>Add Task</button>
Download the project at this stage:zip |github
Once again, at this point you can start the Python server and then navigate tohttp://localhost:5000/index.html
in your browser to see the page.
The above HTML code looks great, but it is static, so it is of no use other than to prototype the look of our application. We now need to convert it into a Knockout view that can show actual data and respond to user clicks. For this we will use some constructs provided by Knockout:
<table> <tr><td></td><td><b>Task</b></td><td><b>Options</b></td></tr> <!-- ko foreach: tasks --> <tr> <td> <span data-bind="visible: done">Done</span> <span data-bind="visible: !done()">In Progress</span> </td> <td><p><b data-bind="text: title"></b></p><p data-bind="text: description"></p></td> <td> <button data-bind="click: $parent.beginEdit">Edit</button> <button data-bind="click: $parent.remove">Delete</button> <span data-bind="visible: done"> <button data-bind="click: $parent.markInProgress">Mark In Progress</button> </span> <span data-bind="visible: !done()"> <button data-bind="click: $parent.markDone">Mark Done</button> </span> </td> </tr> <!-- /ko --></table><button data-bind="click: beginAdd">Add Task</button>
Theko foreach: tasks
directive is inside an HTML comment to avoid making the HTML document illegal. Even as a comment, Knockout will recognize it and interpret it, causing all the HTML elements that appear after it and until the closing/ko
comment to repeat once per item in thetasks
collection (that we haven't defined yet).
Note that the HTML element that will display the task title got adata-bind="text: title"
attribute added. This directive tells Knockout tobind thetitle
variable to the text of the element. Thetitle
variable is obtained from the current context. Since we are inside aforeach
loop the context is the current task in thetasks
collection.
Knockout provides binding constructs that can connect to attributes of an element instead of its text. For example, theIn progress
andDone
labels get theirvisible
attribute bound to thedone
field of the task, so that only the label that reflects the current state of each task is visible while the other one is hidden. The same binding method is used for the buttons that change the task's done state.
To respond to user input Knockout provides theclick
binding, which takes a method name. For example, at the very bottom we have theAdd Task
button, which has aclick
binding to methodbeginAdd
. Like variables, methods are searched in the current context. The button is outside theforeach
loop, so it is in the global context, which as we will see soon is the ViewModel instance that controls the element.
The buttons that are inside theforeach
are more tricky, because the context at the level is each task in thetasks
collection. We could add methods to the task objects, but that is a complication since task objects will be returned by the REST server. Instead, we use the$parent
prefix to indicate that the parent context should be used.
With the above changes our page is depending on a few things that don't exist yet:
tasks
collection that has all the tasks to display, each with fields calledtitle
,description
, anddone
.beginEdit
that opens a dialog box to edit the selected task.remove
that deletes the selected task.markDone
that changes that done state of the selected task totrue
.markInProgress
that changes that done state of the selected task tofalse
.beginAdd
that opens a dialog box to enter a new task.Knockout uses the MVVM pattern. So far we have worked on the V part, which is the View. The M (Model) will come as JSON data delivered by the REST server. The last component of the triangle is the VM, or ViewModel. This is a Javascript object that will hold the bindings between the models and the views, and will also include the event handlers for user generated actions.
Let's write a mock ViewModel that can interact with our view. This code will be inserted at the bottom of the page, in the<script>
area:
function TasksViewModel() { var self = this; self.tasks = ko.observableArray(); self.tasks([ { title: ko.observable('title #1'), description: ko.observable('description #1'), done: ko.observable(false) }, { title: ko.observable('title #2'), description: ko.observable('description #2'), done: ko.observable(true) } ]); self.beginAdd = function() { alert("Add"); } self.beginEdit = function(task) { alert("Edit: " + task.title()); } self.remove = function(task) { alert("Remove: " + task.title()); } self.markInProgress = function(task) { task.done(false); } self.markDone = function(task) { task.done(true); } } ko.applyBindings(new TasksViewModel(), $('#main')[0]);
Thevar self = this;
idiom the begins this function is common in Javascript functions that have callback functions, as it saves the original value ofthis
so that the callbacks, which may have a differentthis
, can use it.
Theko.observableArray()
andko.observable()
are the magic that makes Knockout incredibly easy to use. Observables are special Javascript objects that connect a value from a Model to one or moredata-bind
attributes that refer to it in the View. Once the connection is established any changes to the value will automatically update the view, and any changes to the view will automatically update the value.
Knockout provides theko.observable()
object for single values and theko.observableArray()
for a collection of values. The array version is particularly interesting, because adding or removing array elements will also automatically update what appears in ako.foreach
section.
Something to keep in mind when working with observables is that they look like regular variables, but they are objects. One can be tempted to write this type of code to update an observable:
var myValue = ko.observable(5);alert(myValue); // this does not workmyValue = 6; // this does not work
At first sight this appears to create an observable with the value 5, display the value in an alert window and then update it to 6. In reality, the alert will print an obscure string that represents the observable object itself, and the assignment will just replace the observable object with a regular value of 6. The correct way to access and modify and observable is to invoke it as a function:
var myValue = ko.observable(5);alert(myValue()); // display valuemyValue(6); // update value to 6
This is even more error prone on observable arrays, where the function call parenthesis sometimes have to be inserted before the square brackets:
var myArray = ko.observableArray([1, 2, 3]);alert(myArray()[1]); // show second element
For ourTasksViewModel
object we have defined an observable array calledtasks
that will get connected to theko.foreach: tasks
that we have in our view.
We initialize our tasks collection with a mock Model that includes two task objects, each having observables for its properties, so that there are also automatic updates for thedata-bind
attributes that point to the fields of each task.
We then define the event handlers that will respond to click events. For now thebeginAdd
,beginEdit
andremove
just show an alert dialog. Note that Knockout sends the task object for the events that were generated from inside theko.foreach
.
The last two events are interesting, because instead of just alerting we are actually modifying our model. Since thetask.done
field is an observable the simple act of changing the value will trigger updates to the appropriate parts of the web page.
The last statement in the example callsko.applyBindings
to activate the ViewModel. The first argument is the created ViewModel object and the second argument is the root element in the HTML document to associate with it.
Download the project at this stage:zip |github
This is, again, a good time to check how the application is progressing. Go ahead and start the Python server and then openhttp://localhost:5000/index.html
in your browser to see Knockout in action. You will see that the two example tasks in our Model were automagically rendered to the page, and that all the buttons are active. In particular try the "Mark Done" and "Mark In Progress" buttons, to see how simple it is to update the page when data changes.
Now that we have the client rendering a mock model we are ready to switch to the real thing.
To make an HTTP request to our server we will use jQuery's$.ajax()
function. Here is a second version of ourTasksViewModel
, modified to send a request to the server for the list of tasks:
function TasksViewModel() { var self = this; self.tasksURI = 'http://localhost:5000/todo/api/v1.0/tasks'; self.username = "miguel"; self.password = "python"; self.tasks = ko.observableArray(); self.ajax = function(uri, method, data) { var request = { url: uri, type: method, contentType: "application/json", accepts: "application/json", cache: false, dataType: 'json', data: JSON.stringify(data), beforeSend: function (xhr) { xhr.setRequestHeader("Authorization", "Basic " + btoa(self.username + ":" + self.password)); }, error: function(jqXHR) { console.log("ajax error " + jqXHR.status); } }; return $.ajax(request); } self.beginAdd = function() { alert("Add"); } self.beginEdit = function(task) { alert("Edit: " + task.title()); } self.remove = function(task) { alert("Remove: " + task.title()); } self.markInProgress = function(task) { task.done(false); } self.markDone = function(task) { task.done(true); } self.ajax(self.tasksURI, 'GET').done(function(data) { for (var i = 0; i < data.tasks.length; i++) { self.tasks.push({ uri: ko.observable(data.tasks[i].uri), title: ko.observable(data.tasks[i].title), description: ko.observable(data.tasks[i].description), done: ko.observable(data.tasks[i].done) }); } }); } ko.applyBindings(new TasksViewModel(), $('#main')[0]);
Let's go over the changes from top to bottom.
At the top of theTasksViewModel
class we define a few new member variables. We will usetasksURI
as our root URI to access the REST server. If you recall, this is the URI that when queried with theGET
HTTP method returns the list of tasks. We also have two variables that record the login information, which for now we are setting to values that are known to work (we will improve on this later).
Thetasks
observable array is still there, but we do not initialize it with mock data anymore.
Then we have a new method, simply calledajax
. This is a helper function that wraps jQuery's$.ajax()
call and makes it more convenient. The function takes three arguments, the URI to connect to, the HTTP method to use and optionally the data to send in the body of the request.
Ourajax
function doesn't really do much, it just creates a request object and sends it over to$.ajax()
. In addition to the data given in the function arguments, the request object include the mime type for the request and the response, it disables caching and sets a couple of mysterious callback functions.
The first callback function is calledbeforeSend
, and jQuery will invoke it after it has created thejqXHR
object that will carry over the request. We need to use this callback to insert the HTTP Basic authentication credentials, because without that the server will not accept our request. For now we just encode the hardcoded username and password. We use thebtoa
function to encode the credentials in base64 format, as required by the HTTP protocol.
The second callback function will be invoked by jQuery if the request comes back with an error code. This can happen if the username and/or password are incorrect, for example. In this case we just log the error.
Ourajax
wrapper function returns the return value from$.ajax
, which is apromise object. A promise acts as a proxy for a result of an asynchronous function. When$.ajax()
is invoked a request is sent to the server, but the function returns immediately without waiting for the response. The promise object represents that unknown response.
The last change is at the bottom of the class definition, where the actual request to the REST server is made. Here is that snippet of code copied again:
self.ajax(self.tasksURI, 'GET').done(function(data) { for (var i = 0; i < data.tasks.length; i++) { self.tasks.push({ uri: ko.observable(data.tasks[i].uri), title: ko.observable(data.tasks[i].title), description: ko.observable(data.tasks[i].description), done: ko.observable(data.tasks[i].done) }); } });
We call theajax()
wrapper function with the arguments to send aGET
request to the server on the main URI. Then on the return promise we calldone()
. All jQuery promises provide adone
method that takes a callback. The callback will be invoked once the asynchronous function associated with the promise ends, with the result of the function passed as an argument.
Our done callback will receive the response data, which is a JSON object that contains the array of tasks returned by the server. We then take this array and for each element create a similar object to add to ourtasks
observable array. Note that we can't add the task returned by the server directly because we need some of the fields in the tasks to be observables, so that they update the page automatically when they change.
Download the project at this stage:zip |github
If you try the application with these changes you will see the task list from the server rendered in the page.
Adding a new task is fun because it requires us to create a dialog box where the user can enter the details.
The Boostrap documentation provides all the information required to create modal dialog boxes. Below is the code that creates a dialog box with a form inside to enter task information:
<div tabindex="=1" role="dialog" aria-labelledby="addDialogLabel" aria-hidden="true"> <div> <button type="button" data-dismiss="modal" aria-hidden="true">×</button> <h3>Add Task</h3> </div> <div> <form> <div> <label for="inputTask">Task</label> <div> <input data-bind="value: title" type="text" placeholder="Task title"> </div> </div> <div> <label for="inputDescription">Description</label> <div> <input data-bind="value: description" type="text" placeholder="Description"> </div> </div> </form> </div> <div> <button data-bind="click: addTask">Add Task</button> <button data-dismiss="modal" aria-hidden="true">Cancel</button> </div></div>
This dialog box will be hidden initially. The code can be inserted anywhere in the page, for example a good place is right after themain
div.
If you look carefully you will see that the<input>
elements havedata-bind
attributes in them that connect to their values. This is so that we can bind variables to them. Likewise, the Add Task button at the bottom is bound to its click event, so that we can act when the user accepts the dialog box.
According to the Bootstrap documentation to display the dialog box we have to do this:
$('add').modal('show');
Since we want to display this dialog box when the user clicks the Add Task button we can now replace our alert with the real thing:
self.beginAdd = function() { $('#add').modal('show'); }
This dialog box is effectively our second View, it is a different entity than our task list. Since this is a different View, we also need a different Model and ViewModel:
function TasksViewModel() { // ... same contents and before self.add = function(task) { self.ajax(self.tasksURI, 'POST', task).done(function(data) { self.tasks.push({ uri: ko.observable(data.task.uri), title: ko.observable(data.task.title), description: ko.observable(data.task.description), done: ko.observable(data.task.done) }); }); } } function AddTaskViewModel() { var self = this; self.title = ko.observable(); self.description = ko.observable(); self.addTask = function() { $('#add').modal('hide'); tasksViewModel.add({ title: self.title(), description: self.description() }); self.title(""); self.description(""); } } var tasksViewModel = new TasksViewModel(); var addTaskViewModel = new AddTaskViewModel(); ko.applyBindings(tasksViewModel, $('#main')[0]); ko.applyBindings(addTaskViewModel, $('#add')[0]);
The newAddTaskViewModel
is extremely simple. It has two observables, which connect to the two<input>
tags in the dialog box. It also has one event handler that connects to the Add Task button.
When the button is clicked the event handler simply calls theadd()
method of thetasksViewModel
, passing the title and description values for the new task. The function finally resets the values of the two fields, so that the next time the user wants to add a task the fields appear empty again.
Theadd()
method oftasksViewModel
invokes theajax()
function again, but this time it issues aPOST
request, which according to the REST standard practices corresponds to a request to add a new resource. The response from the request will be the added task, so when ourdone()
callback is invoked we just push a new task into ourtasks
array. Due to the data binding that exists between the array and theko.foreach
in the HTML portion of the document as soon as we add an element to the array that element is rendered to the page.
The changes to edit an existing tasks are a bit more complex, but are largely similar.
A new dialog box is added to the document (we cannot use the same as the Add Task dialog because for editing we have the "done" checkbox that we don't have when adding a task).
We also have to add a new ViewModel:
function EditTaskViewModel() { var self = this; self.title = ko.observable(); self.description = ko.observable(); self.done = ko.observable(); self.setTask = function(task) { self.task = task; self.title(task.title()); self.description(task.description()); self.done(task.done()); $('edit').modal('show'); } self.editTask = function() { $('#edit').modal('hide'); tasksViewModel.edit(self.task, { title: self.title(), description: self.description() , done: self.done() }); } }
And here are the new and updated methods in theTasksViewModel
class to support task editing:
self.beginEdit = function(task) { editTaskViewModel.setTask(task); $('#edit').modal('show'); } self.edit = function(task, data) { self.ajax(task.uri(), 'PUT', data).done(function(res) { self.updateTask(task, res.task); }); } self.updateTask = function(task, newTask) { var i = self.tasks.indexOf(task); self.tasks()[i].uri(newTask.uri); self.tasks()[i].title(newTask.title); self.tasks()[i].description(newTask.description); self.tasks()[i].done(newTask.done); }
The additional complication with the edit dialog box is that prior to showing it we have to fill out the form fields with the current data for the selected task. This is done in thesetTask()
method of theEditTaskViewModel
class.
Once the dialog box is accepted the handler in theEditTaskViewModel
class calls theedit()
method of theTasksViewModel
, which sends aPUT
request into the server to update the task. Note how the URI for this request comes from the task itself, since each task has a unique URI. Finally, the callback for the ajax request calls a new helper functonupdateTask()
to refresh the observables associated with the task that was edited.
Another method that we can complete is the one that deletes a task. This one is easy, as it does not require any HTML changes to the document:
self.remove = function(task) { self.ajax(task.uri(), 'DELETE').done(function() { self.tasks.remove(task); }); }
And even though the "Mark Done" and "Mark In Progress" buttons appear to work, they aren't really working, all they are doing right now is change the model, but they do not communicate these changes to the server. Here are the updated versions that talk to the server:
self.markInProgress = function(task) { self.ajax(task.uri(), 'PUT', { done: false }).done(function(res) { self.updateTask(task, res.task); }); } self.markDone = function(task) { self.ajax(task.uri(), 'PUT', { done: true }).done(function(res) { self.updateTask(task, res.task); }); }
Download the project at this stage:zip |github
The client application is nearing completion now. You can take a break and spend some time playing with the different options, since all of them are working now.
And we now arrive to our final battle. Up to now the authentication credentials that we were sending to the REST server are hardcoded into the client application. We happen to know that these work, but in a real life situation we have to prompt the user to provide his own credentials.
Let's start by removing the hardcoded credentials:
self.username = ""; self.password = "";
We will also have to add a login dialog box, which I'm not going to show here since it is similar to the previous dialog boxes we created.
We need a ViewModel for our dialog box:
function LoginViewModel() { var self = this; self.username = ko.observable(); self.password = ko.observable(); self.login = function() { $('#login').modal('hide'); tasksViewModel.login(self.username(), self.password()); } }
And finally we need to add a few support methods to ourTasksViewModel
class:
function TasksViewModel() { // ... no changes here self.beginLogin = function() { $('#login').modal('show'); } self.login = function(username, password) { self.username = username; self.password = password; self.ajax(self.tasksURI, 'GET').done(function(data) { for (var i = 0; i < data.tasks.length; i++) { self.tasks.push({ uri: ko.observable(data.tasks[i].uri), title: ko.observable(data.tasks[i].title), description: ko.observable(data.tasks[i].description), done: ko.observable(data.tasks[i].done) }); } }).fail(function(jqXHR) { if (jqXHR.status == 403) setTimeout(self.beginLogin, 500); }); } self.beginLogin(); }
Let's see how this works. At the bottom ofTasksViewModel
we had the code that issued the request to get the task list. We now replaced that with a call tobeginLogin()
which starts the login process by displaying the login dialog box.
When the user accepts the dialog box methodlogin()
will be called, with the entered credentials. Only now we are ready to talk to the server, so we issue the request for the task list after we update our login variables. If the request succeeds then we populate ourtasks
array as we did before.
If the request fails we now have a second callback attached to the ajax request promise. Thefail
callback in a promise executes if the asynchronous function returns an error. What we do in this case is check the error code from the ajax request object and if found to be 403 we just callbeginLogin()
again to ask for new credentials. Note we don't show the dialog immediately, instead we wait 0.5 sec with a timeout. This is just because the dialog box has animated events to appear and disappear, so we need to make sure the "show" animation does not collide with the "hide" animation for the previous dialog instance.
I just said that login errors return a 403 code. Those that are familiar with HTTP error codes will jump to correct me. Error 403 is the error code for the "Forbidden" error. The correct HTTP code for the "Not Authorized" error is 401. The problem with error code 401 is that browsers display their own login dialog box when they receive this error, even if the error came from an ajax request. We don't want the browser to show its login dialog since we have our own, so we trick the browser by having the server send error 403 instead of 401.
Download the completed project:zip |github
If we were to deploy this application on a real web server for real users we have to take some security measures.
The REST server takes user credentials according to the HTTP Basic Authentication protocol, which sends usernames and passwords in clear text. Of course this is not acceptable, we should not risk leaking confidential information from our users to third parties.
The proper way to address this problem is by putting the server on secure HTTP, which ensures that communication between client and server is encrypted.
The recommended way to implement secure HTTP is with a proxy server. Our server remains the same, when it starts it listens, for example, onhttp://localhost:5000
, which is only reachable to other processes in the same machine. Then another web server is installed and configured to listen on the secure HTTP port and do all the proper encryption procedures. This server is then configured to act as a proxy for our server, so to us nothing changes, we just receive requests from the proxy server, which in turn receives requests from the user over an encrypted channel.
There are other measures to take if we were to deploy this application on a real server, at the very least the Flask development web server should be replaced with a more robust server. I recommend that you see thedeployment chapters of my Flask Mega-Tutorial to learn about this topic.
Phew! Can you believe we are done?
The application is now fully functional and exercises all the functions of our REST server. A nice enhancement that I will leave as an exercise for those interested would be to save the login credentials in a browser cookie, so that the user does not need to enter them every time. You get bonus points if you add a "Remember Me" checkbox to the login dialog.
Writing rich client side applications in Javascript can be fun. Javascript is an interesting language that at first may seem odd to those of us used to more traditional languages, but once you familiarize with it you find it can do many cool things.
I hope this article and example code along with the previous one serve as a good introduction to the use of REST APIs. If you have any questions or suggestions you are welcome to write below in the comments area.
Miguel
Thank you for visiting my blog! If you enjoyed this article, please consider supporting my work and keeping me caffeinated with a small one-time donation throughBuy me a coffee. Thanks!
#1Dogukan Tufekci said2013-06-16T11:34:14Z
Miguel you keep amazing me! Thanks for this timely tutorial upon the mega tutorial.
I tried to run the server by downloading the zips. However I have an import error:
ImportError: No module named flask.ext.httpauth
How can I fix this? Thanks again!
#2Miguel Grinberg said2013-06-16T16:29:51Z
@Dogukan: You need to install the Flask-HTTPAuth module (which I wrote). You can do so by running "pip install -r requirements.txt". If that does not work then maybe pip reported an error and couldn't install the module, check the logs and let me know what the error is in that case.
#3Dogukan Tufekci said2013-06-17T02:42:31Z
@Miguel here is the solution:
flask/bin/pythonrest-server.py
I've been trying to run it with pythonrest-server.py as it was instructed by README on github. Thanks for the tutorial, again!
#4Miguel Grinberg said2013-06-17T05:56:55Z
My bad. I have updated the instructions on github.
#5Roman said2013-06-18T00:10:36Z
also thx for you work
#6Daniel Gabriele said2013-06-18T03:53:41Z
As someone writing a REST API client/server, it's great to see another's perspective on design. I thought your approach made a lot of sense and was easy to follow. One thing you might add to the API server tutorial is a mention of batch operations. Gratias tibi.
#7Ciprian Amariei said2013-06-18T20:29:11Z
I would recommend angularjs with it's services for a REST client in javascript.
#8Miguel Grinberg said2013-06-19T02:56:28Z
@Ciprian: Sure, Angular is a valid option. I personally found Knockout + jQuery for the REST calls simpler and equally featured. I plan to re-evaluate Angular when I need to build a big client app.
#9Jesper Puge said2013-06-27T21:22:17Z
Very well written, I laernes a lot.
I see how the HTML was your view and you had some models, but how abolut the controller?
#10Miguel Grinberg said2013-06-28T04:08:40Z
@Jesper: The model that Knockout uses is called MVVM, or Model-View-ViewModel. In my example above the Model is represented by the data elements, the View is the HTML template, and the ViewModel is the class that has the data and the callbacks.
#11Maxim Koltsov said2013-07-02T08:45:55Z
Oh wow, thanks for your wonderful tutorials!
Do you plan to make tutorial about token-based auth, like OAuth2? I'd like to see, how JS client application deals with getting token from server, refresh tokens, etc...
Thanks!
#12Miguel Grinberg said2013-07-03T04:45:11Z
@Maxim: There isn't any significant difference in the way the API is coded if you use OAuth. You can run your own oauth provider or you can use a 3rd party one like Facebook, Twitter etc. The login part cannot be done in the REST API because logins with OAuth are interactive, you'll have to build a small server-side web application that perfoms the login. Once the authentication process succeeded the server can pass a username (or email address) and an access token to the JS client app that identify this user. The token can be the OAuth access token if you want, but it does not really need to be since this token controls access to your own API, not the OAuth provider. In my opinion a randomly generated token (os.urandom() based) is more secure. The token will have an expiration, and will be stored in the user database. Once the client app has the username/email and token it can send requests as explained above, sending the credentials with every request as the HTTP Basic Auth. I hope this helps.
#13Michael Fluch said2013-07-03T17:55:59Z
excellent tutorial, well preparete and elaborated... learned a lot... thx
#14Kris Magnusson said2013-08-18T20:56:15Z
Hi Miguel, I like your tutorial. It explained a lot about RESTful programming using Knockout + jQuery that I didn't know coming from a pure Java world. However, I seem to find myself at philosophical odds with the notion of MVVM instead of MVC. It just seems like a much cleaner division of function to use a model to hold the data, the view to represent the UI elements, and the controller to contain all the logic necessary to do the work. Maybe I'm prejudiced by my Java training, which I learned in 1996, and the Gang of Four Design Patterns book, but this Model-View-ViewModel seems like a hack. Why shouldn't we strive for a pure MVC architecture instead of the model that Knockout uses? Thanks, Kris
#15Miguel Grinberg said2013-08-19T22:50:06Z
@Kris: Ages ago I used to write Smalltalk, using pure MVC. I do not honestly see much of a difference between MVC, MVP and MVVM, they all share the goal of separating data, presentation and logic. The MVVM model is particularly suited for frameworks that offer two-way data binding, but to me that is just a technicality, call it whatever you want, but the goal is always to have clear separation of concerns, and all the MV* model achieve that.
#16rayan said2013-10-21T16:49:46Z
I was able to follow the instructions until the conection to server. My server seems to work well. i can use curl with out any issue. but through js, i am not receiving the tasks. no error message or anything. when i send the request my server is showing return code 200
any thoughts
#17Miguel Grinberg said2013-10-22T01:26:44Z
@rayan: you need to determine if this is a client or server side problem. Did you check the js console for errors? That may give you some clues.
#18Al said2013-10-25T17:05:13Z
This is very good. Coming from a J2EE environment it helped me understand the AJAX point of you.
Do you have any recommendations on books that explain the layout of the AJAX items. Like beforeSend, error, done etc.
I'd prefer a good book as opposed to just the tutorials and code examples.
Thanks
#19Miguel Grinberg said2013-10-26T04:06:37Z
@Al: I can't really recommend a "good" book. There are a lot of them, but the technology is still very immature in my opinion. You could look at AngularJS books, since this is one of the leading frameworks. The book by Brad Green is decent. If you want something more lower level then look for Backbone or even lower, jQuery.
#20Scott Miao said2013-11-02T09:47:38Z
http://127.0.0.1:5000 is not allowed by Access-Control-Allow-Origin
#21Miguel Grinberg said2013-11-03T01:25:28Z
@Scott: you need to open the html page through http://localhost:5000, so that both the page and the REST server are on the same host and port.
#22Roi Kliper said2013-11-04T17:05:38Z
Hi Miguel, I followed your tutorial which was brilliant.
I have how ever encountered a bug which I think touched a fundamental point in understanding the flask framework. You seem to store the current_user inside g it seem that sometimes the g persists across different users and so one user can end up logged in and generate content as a different user. I'd be happy to discuss and replicate the issue.
Cheers,
Roi
#23Miguel Grinberg said2013-11-05T05:15:34Z
@Roi: can you give me an example? The "g" object is not a global variable, each thread gets its own version. So unless you've found a bug what you describe is not possible.
#24Zulu said2013-11-05T09:45:36Z
@Scott Miao: You have to change "localhost" to "127.0.0.1" in index.html:125
#25ousmane SENE said2013-11-28T11:15:54Z
Thanks for this tutorial.
If you would you like to support myReact Mega-Tutorial series on this blog and as a reward have access to the complete tutorial in book and/or video formats, you can now order it from myCourses site or fromAmazon.
© 2012- by Miguel Grinberg. All rights reserved.Questions?