CoffeeScript, a new programming language built on top of JavaScript, offers a clean syntax that should appeal to anyone who likes Python or Ruby. It also provides many functional programming features inspired by languages such as Haskell and Lisp.
In Part 1 of this series, you learned about the benefits of using CoffeeScript. You also set up the development environment and ran scripts. In Part 2, you explored the CoffeeScript programming language by trying many of the features while solving mathematical problems. In Part 3, you wrote the client-side code for a web application.
In this final article, you'll write the server-side component and finish the application—all in CoffeeScript.
Download the source code used in this article.
The web application in Part 3 used a keyword to perform a search on both Google and Twitter. For the client side of the application, you simply mocked up the results from the server. To actually implement such functions, you need the server side of the application to call web services provided by Google and Twitter. Both companies offer very simple search services. All you need to do is make HTTP GET requests to the search services. Listing 1 shows a generic function for making HTTP GET requests.
Listing 1. Fetching web resources
http = require "http"
fetchPage = (host, port, path, callback) ->
options =
host: host
port: port
path: path
req = http.get options, (res) ->
contents = ""
res.on 'data', (chunk) ->
contents += "#{chunk}"
res.on 'end', () ->
callback(contents)
req.on "error", (e) ->
console.log "Erorr: {e.message}"
|
At the top of the script is the require
statement, which you saw briefly in Part 1 of this series. This is Node.js's module import syntax, or
at least the CoffeeScript version of it. The "native" version would be
var http
= require("http");. In this article,
you'll use several Node.js core modules. (Detailed information about how
the modules work is outside the scope of this article.) All of the modules
used in this article should be available to you if you installed Node.js
(see Part 1). For the example in Listing 1, you're using the
http module, which provides several useful
classes and functions for both making and accepting HTTP requests.
Listing 1 then defines a
fetchPage function that accepts four
parameters:
- The
hostname of the resource. - The resource's
port. - The resource's
path. - A
callbackfunction.Any type of I/O function in Node.js is going to be asynchronous in nature and thus will need a
callbackfunction to invoke when it's finished. ThefetchPagefunction accepts acallbackfunction as its fourth parameter. It then uses the first three parameters to make an HTTP GET request using thehttpmodule'sgetfunction.
The fetchPage function also takes a
callback function that gets passed an instance
of ClientResponse.
ClientResponse, which is an object defined in
the http module, implements the
ReadableStream interface (a core interface in
Node.js). It is an asynchronous interface that receives two events:
data and end. Its
only function is used to register callback to these events. The data event
occurs when data is received from the resource for which you made the HTTP
GET request.
All of the data might be returned at once from the resource, but more
commonly, the data will be sent in chunks. When each chunk is received,
the data event is fired and the callback is invoked. You created a
variable called contents; each time you receive
another chunk, you simply append it to
contents. When all of the data has been
received, the end event is fired. You now have
all of the data, so you can pass contents back
to the callback function that was passed in to
the fetchPage function. With this multipurpose
function defined, let's create some more specialized functions for the
Google and Twitter search APIs, as in Listing 2.
Listing 2. Google and Twitter search functions
googleSearch = (keyword, callback) ->
host = "ajax.googleapis.com"
path = "/ajax/services/search/web?v=1.0&q=#{encodeURI(keyword)}"
fetchPage host, 80, path, callback
twitterSearch = (keyword, callback) ->
host = "search.twitter.com"
path = "/search.json?q=#{encodeURI(keyword)}"
fetchPage host, 80, path, callback
|
There are two functions defined in Listing 2:
-
googleSearch, which takes akeywordand acallbackfunction. It fixes the host, dynamically creates the path using CoffeeScript's string interpolation, and then usesfetchPage. -
twitterSearch, which is very similar togoogleSearchbut with different host and path values.
For both path values, you use string interpolation and JavaScript's handy
encodeURI function to handle any spaces or
other special characters. Now that you have these search functions, you
can create a specialized function for a combined search scenario.
Combining asynchronous functions
There are a couple of ways that you could do a combined search on Google
and Twitter. You could call googleSearch and
then, in the callback, invoke
twitterSearch, or the other way around.
However, the asynchronous/callback architecture of Node.js lets you do
things more elegantly and more efficiently. Listing 3 shows the combined search.
Listing 3. Searching both Google and Twitter
combinedSearch = (keyword, callback) ->
data =
google : ""
twitter : ""
googleSearch keyword, (contents) ->
contents = JSON.parse contents
data.google = contents.responseData.results
if data.twitter != ""
callback(data)
twitterSearch keyword, (contents) ->
contents = JSON.parse contents
data.twitter = contents.results
if data.google != ""
callback(data)
|
The combinedSearch function has a now-familiar
signature: accepting a keyword and a callback. It then creates a data
structure for the combined search results called
data. The data
object has a google field and a
twitter field, both of which are initialized as
empty strings. The next step invokes the
googleSearch function. In its callback, you
parse the results from Google using the standard
JSON.parse function. The JSON text returned
from Google is parsed into a JavaScript object. Use this to set the value
of the data.google field. After invoking
googleSearch, invoke
twitterSearch. Its
callback function is very similar to the one
for googleSearch.
It's important to understand that in both callbacks you check to see if
you have data from the other callback. You don't know which one will
finish first. So, check to see if you have data from both Google and
Twitter. Once you do, you invoke the callback
function passed in to the combinedSearch
function. You now have a function that will search both Google and Twitter
and give the combined results. The next task is to expose this to the web
page you created in Part 3 of this series. All you have to do is write a web
server.
At this point, you have:
- A web page that can send keywords and show search results.
- A function that can take a keyword and produce search results from Google and Twitter.
What glues these things together? You can call it a web server, an application server, or even middleware. Whatever you want to call it, it doesn't take much CoffeeScript to write it.
The web server needs to serve two purposes. Obviously, it needs to accept requests for a combined search. It also needs to provide the static resources that you created in Part 3. You're creating a web application, so you have to be mindful of the same origin policy. The search calls must go to the same place that produced the web page. Let's handle the static resources first. Listing 4 shows a function for serving static resources.
Listing 4. Serving static resources
path = require "path"
fs = require "fs"
serveStatic = (uri, response) ->
fileName = path.join process.cwd(), uri
path.exists fileName, (exists) ->
if not exists
response.writeHead 404, 'Content-Type': 'text/plain'
response.end "404 Not Found #{uri}!\n"
return
fs.readFile fileName, "binary", (err,file) ->
if err
response.writeHead 500,
'Content-Type': 'text/plain'
response.end "Error #{uri}: #{err} \n"
return
response.writeHead 200
response.write file, "binary"
response.end()
|
The serveStatic function handles the requests
for static resources in the web application. Notice that you need to use
two more Node.js modules:
pathis simply a utility library for dealing with file paths.- File system, or
fs, provides all of the file I/O in Node.js and is basically a wrapper on top of standard POSIX functions.
The serveStatic function takes two parameters:
uriis essentially a relative path to the static file that is being requested by a web browser.- A
ServerResponseobject, which is another type defined in thehttpmodule. Among other things, it gives you a stream to write data to whatever made the HTTP GET request for the resource.
In serveStatic, turn the relative path to the
file into an absolute path using process.cwd.
The process object is a global object that
represents the system process that Node.js is running in. Its
cwd method gives the current working directory.
Use the path module to combine the current
working directory and the relative path to the file you want; the result
is the absolute path. With the absolute path you can use the
path module again to check if the file exists
or not. Checking whether a file exists involves I/O, so it is an
asynchronous function. Pass it the fileName and
a callback function. The callback gets a Boolean value, letting you know
if the file exists or not. If it does not exist, then you want to write an
HTTP 404 "file not found" message.
If the file does exist, then you need to read its contents using the
fs module and its
readFile method, which is asynchronous. It
takes the fileName, a type, and a callback
function. The callback receives two parameters:
- An error parameter indicating any problems reading the resource from the file system. If there is a problem, an HTTP 500 error message is written back to the client.
- If there were no problems, an HTTP 200 OK message is written and the contents of the file are sent back to the client.
This function handles the relatively easy case of serving static files. The next section discusses the more difficult scenario where you need to dynamically respond to a search request.
Dynamic responses and the server
The example web server primarily handles requests to static resources and
dynamic search requests. The strategy is to use a specific URL for
handling the search requests and then offload the other requests to the
serveStatic function. Use a relative URL of
/doSearch for the search requests. Listing 5 shows the web server code.
Listing 5. CoffeeScript web server
url = require "url"
server = http.createServer (request, response) ->
uri = url.parse(request.url)
if uri.pathname is "/doSearch"
doSearch uri, response
else
serveStatic uri.pathname, response
server.listen 8080
console.log "Server running at http://127.0.0.1:8080"
|
Once again, the script starts by loading a Node.js module. The
url module is a useful library for parsing
URLs. The next step is to create the web server using the
http module that you loaded in Listing 1. Use that module's
createServer method, which takes a callback
function that will be invoked each time a request is made to the web
server. The callback function takes two parameters: an instance of
ServerRequest and an instance of
ServerResponse. Both of the types are defined
in the http module. In the callback, parse the
URL of the request made to the server by using the
parse method of the
url module. This gives you a URL object, and
you can use its pathname property to get the
relative path. If the pathname is
/doSearch, you call the
doSearch function (discussed below). Otherwise,
invoke the serveStatic function from Listing 5. Listing 6 shows how doSearch
works.
Listing 6. Handling search requests
doSearch = (uri, response) ->
query = uri.query.split "&"
params = {}
query.forEach (nv) ->
nvp = nv.split "="
params[nvp[0]] = nvp[1]
keyword = params["q"]
combinedSearch keyword, (results) ->
response.writeHead 200, 'Content-Type': 'text/plain'
response.end JSON.stringify results
|
The doSearch function parses the query string
for the URL, which can be found in the query property of the
uri object. Break this up by splitting the
string on ampersands. Then split each of the substrings on the equals sign
to get the name-value pairs from the query string. Store each of these in
the params object. Pull out the
"q" parameter to get the keyword that you want
to search for. Pass this to the combinedSearch
function from Listing 3. You
have to pass this a callback function. The example callback function
simply writes an HTTP 200 OK and turns the search results into a string
using the standard function JSON.stringify.
That's all you need for the server. In the next section, see how to hook this into the client code from Part 3 of this series.
In Part 3 you had a MockSearch class that
used mock data to provide search results. Now you'll define a new class to
do a real search that calls the search server. Listing 7 shows the new search class.
Listing 7. The real search class
class CombinedSearch
search: (keyword, callback) ->
xhr = new XMLHttpRequest
xhr.open "GET", "/doSearch?q=#{encodeURI(keyword)}", true
xhr.onreadystatechange = ->
if xhr.readyState is 4
if xhr.status is 200
response = JSON.parse xhr.responseText
results =
google: response.google.map (result) ->
new GoogleSearchResult result
twitter: response.twitter.map (result) ->
new TwitterSearchResult result
callback results
xhr.send null
|
The CombinedSearch class has a single method
search that has the same signature as the
MockSearch search method. It takes a keyword
and a callback function. Inside the function:
- Use
XMLHttpRequest, an old friend of any web developer, to make an HTTP request to the server using the/doSearchpath and the keyword that was passed in the function. - When you get a response, parse it using
JSON.parse. - Create a results object with
googleandtwitterfields. Create them using theGoogleSearchResultandTwitterSearchResultclasses from Part 3. - Simply pass results back to the
callbackfunction.
Now, you just need to use this in the web page's
doSearch method instead of the
MockSearch. Listing 8 shows how to use the
CombinedSearch class.
Listing 8. Using the CombinedSearch class
@doSearch = ->
$ = (id) -> document.getElementById(id)
kw = $("searchQuery").value
appender = (id, data) ->
data.forEach (x) ->
$(id).innerHTML += "<p>#{x.toHtml()}</p>"
ms = new CombinedSearch
ms.search kw, (results) ->
appender("gr", results.google)
appender("tr", results.twitter)
|
If you compare Listing 8 to the
doSearch from Part 3, you won't see much of a difference. The only thing that's
different is in the seventh line. Instead of instantiating an instance of
MockSearch, you instantiate an instance of
CombinedSearch. Everything else is the same.
You get the keyword from the web page, call the search, and then append
the results by invoking the toHtml method of
each SearchResult object. Figure 1 shows the web application with "live" search
results coming from the server.
Figure 1. Running the example web application
To get the changes you made to the client code, you need to recompile it
with coffee -c search.coffee. To run the
application, use coffee search-server.coffee.
Then you can open a browser to http://127.0.0.1:8080 and try some
different queries.
In this article, you finished the web application by building the server-side component to complement the client code from Part 3. Now, at the end of this series, you have a complete application—all written in CoffeeScript. You used a lot of the features of Node.js that enabled you to use CoffeeScript as a server-side technology.
A common criticism of Node.js is that its non-blocking style leads to layers and layers of callback functions. They can be hard to sort out in your head, and JavaScript's verbose syntax can make it even more difficult. CoffeeScript doesn't change the need to use all of the callbacks, but its elegant syntax does make it a little easier to write and understand such code.
| Description | Name | Size | Download method |
|---|---|---|---|
| Article source code | cs4.zip | 7KB | HTTP |
Information about download methods
Learn
- Check out the previous parts of this
series:
- Part 1: Getting started
- Part 2: Learn the language with hands-on examples
- Part 3: Using CoffeeScript on the client
-
Node.js: Use this launching point to
learn more about the application.
- "Just what is Node.js?" (developerWorks, May 2011): Understand how
Node is a server-side JavaScript interpreter that changes the notion of
how a server should work.
- "Use Node.js as a full cloud environment development stack"
(developerWorks, April 2011): See how Node.js can be combined with cloud
technologies.
- "High-performance Web development with Google Web Toolkit and
Eclipse" (developerWorks, October 2009): Compiling into JavaScript
is not a new idea. So if you are a fan of the Java programming language,
you should read this article.
- "All aboard! An introduction to Rails 3" (developerWorks, March
2010): CoffeeScript is now part of Ruby on Rails. Check out the other new
features in Rails in this article.
-
CoffeeScript project
on Github: Start here to learn about CoffeeScript.
- "Create Ajax applications for the mobile Web" (developerWorks,
March 2010): Get more information about using Ajax in mobile web
applications.
-
developerWorks Web
development zone: Find articles covering various web-based
solutions. See the Web
development technical library for a wide range of technical
articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks
podcasts: Listen to interesting interviews and discussions for
software developers.
-
developerWorks technical events and webcasts: Stay current with
developerWorks technical events and webcasts.
- developerWorks on
Twitter: Join today to follow developerWorks tweets.
- developerWorks
on-demand demos: Watch demos ranging from product installation and
setup for beginners to advanced functionality for experienced
developers.
Get products and technologies
-
Node.js: Download Node.js
and start easily building scalable network programs.
- IBM product
evaluation versions: Download or explore
the online trials in the IBM SOA Sandbox and get your hands on
application development tools and middleware products from DB2, Lotus,
Rational, Tivoli, and WebSphere.
Discuss
- The developerWorks
community: Connect with other developerWorks users while exploring
the developer-driven blogs, forums, groups, and wikis.
- Find other developerWorks members interested in web development.

Michael Galpin is a software engineer at Google. He is a co-author of the book Android in iPractice and a frequent contributor to developerWorks. For a sneak peek at what he's up to next, check out his blog or follow him on Twitter @michaelg or Google+ Michael Galpin.



