Your first cup of CoffeeScript, Part 4: Using CoffeeScript on the server

This series explores the popular CoffeeScript programming language, which is built on top of JavaScript. CoffeeScript compiles into JavaScript that is efficient and consistent with many best practices. You can run this JavaScript in a web browser or use it with technologies such as Node.js for server applications. In previous parts of this series, you learned the benefits of CoffeeScript, set up the development environment, exercised many of the features, and wrote the client-side code for a real application using CoffeeScript. In this final part of the series, it's time to write the server-side CoffeeScript.

Share:

Michael Galpin, Software Engineer, Google

Michael Galpin's photoMichael 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.



21 February 2012

Also available in Chinese Russian Japanese Vietnamese

Introduction

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.


Calling all web services

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 host name of the resource.
  • The resource's port.
  • The resource's path.
  • A callback function.

    Any type of I/O function in Node.js is going to be asynchronous in nature and thus will need a callback function to invoke when it's finished. The fetchPage function accepts a callback function as its fourth parameter. It then uses the first three parameters to make an HTTP GET request using the http module's get function.

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 a keyword and a callback function. It fixes the host, dynamically creates the path using CoffeeScript's string interpolation, and then uses fetchPage.
  • twitterSearch, which is very similar to googleSearch but 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.


A CoffeeScript 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:

  • path is 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:

  • uri is essentially a relative path to the static file that is being requested by a web browser.
  • A ServerResponse object, which is another type defined in the http module. 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.


Calling the search server

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 /doSearch path and the keyword that was passed in the function.
  • When you get a response, parse it using JSON.parse.
  • Create a results object with google and twitter fields. Create them using the GoogleSearchResult and TwitterSearchResult classes from Part 3.
  • Simply pass results back to the callback function.

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
Running our 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.


Conclusion

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.


Download

DescriptionNameSize
Article source codecs4.zip7KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=794410
ArticleTitle=Your first cup of CoffeeScript, Part 4: Using CoffeeScript on the server
publish-date=02212012