Dynamic content

There's not much point in a static HTTP server written in Node. Node's strength lies in its ability to rapidly serve dynamic content.

Let's add a small filtering feature to our server.

We'll begin by copying the main rest-server folder created in the main recipe to a new folder called rest-server-dynamic-content.

Let's modify the top of rest-server-dynamic-content/index.js to look like so:

const http = require('http') 
const qs = require('querystring')
const url = require('url')

Next let's add a mocked out user list resource:

const userList = [ 
{'id': 1, 'first_name': 'Bob', 'second_name': 'Smith', type: 'red'},
{'id': 2, 'first_name': 'David', 'second_name': 'Clements', type:
'blue'}
]

Next we'll rearrange the http.createServer request handler function to the following:

const server = http.createServer((req, res) => { 
if (req.method !== 'GET') return error(res, 405)
if (req.url === '/') return index(res)
const {pathname, query} = url.parse(req.url)
if (pathname === '/users') return users(query, res)

error(res, 404)
})

We've moved the / route logic above the /users route logic, because the conditional check against req.url is very cheap. We want to prioritize low cost routes rather than penalize them with prior routes that require additional parsing.

We then use url.parse to break up req.url into an object. We're particularly interested in the pathname and query portions of the URL. Once we have a pathname, we can do a direct comparison (no regular expressions required), and pass the query to the users route handling function. Notice we've modified the users function signature to additionally accept a query argument.

Let's now modify our users route handling function:

function users (query, res) { 
const {type} = qs.parse(query)
const list = !type ? userList : userList.filter((user) => user.type
=== type)
res.end(`{"data": ${JSON.stringify(list)}}`)
}

By default, the query portion of a URL string that has been parsed with url.parse is a string. So, to find the type argument in the query string, we use qs.parse to convert it to an object, then use ES2015 deconstruction to assign the type property to a type constant. Imagine we have a route /users?type=blue, our query parameter will be ?type=blue, and the result of passing that to qs.parse will be an object: {type: 'blue'}. This means our type reference will be the string: 'blue'. If there is no query string, or there's a query string with no type argument then the type value will be undefined (which is coerced to a falsey value in the ternary condition). If we have a type, we filter our userList, or otherwise set list to the entire userList. Finally, we call JSON.stringify on our list array, as we pass the entire JSON payload to res.end.

We can try this out like so:

$ node index.js 
$ curl http://localhost:8080/users?type=blue
{"data": [{"id":2,"first_name":"David","second_name":
"Clements","type":"blue"}]}

For bonus points, we can refine our code a little more.

The url.parse module can run the querystring module's parse function for us.

Let's copy rest-server-dynamic-content/index.js to rest-server-dynamic-content/terser-index.js to begin refining.

First we'll remove the querystring module. Our require statements at the top of the file should look like so:

const http = require('http') 
const url = require('url')

Next in our http.createServer request handler function, we alter the line where url.parse occurs to the following:

  const {pathname, query} = url.parse(req.url, true) 

We've added a second argument to url.parse, with a value of true.

Finally, we no longer need to manually parse the query string in our users route handling function, so we'll update users to:

function users ({type}, res) { 
const list = !type
? userList
: userList.filter((user) => user.type === type)
res.end(`{"data": ${JSON.stringify(list)}}`)
}

This server will behave in exactly the same way.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset