Categories
Restify

Restify — Content Negotiation and Errors

Restify is a simple Node back end framework.

In this article, we’ll look at how to handle content negotiation and errors with Restify.

Content Negotiation And Formatting

Restify will determine the content-type to respond with by from highest to lowest priority.

It’ll use the res.contentType if it’s present.

The Content-Type response header if it’s set.

application/json will be returned if the body is an object and not a Buffer instance.

Otherwise, it negotiates the content-type by matching the available formatters with the requests’s accept header.

If a content-type can’t be determined then Restify will respond with an error.

If a content-type can be negotiated, then it determines what formatter to use to format the response’s content.

If there’s no formatter matching the content-type can be found, then Restify will override the response’s content-type to 'application/octet-stream' and then error out if no formnatter if found for that content-type .

The default behavior can be changed by passing the strictFormatters: false property when creating the Restify server instance.

If no formatter is found for the content-type , then the response is flushed without applying any formatter.

Restify ships with application/json , text/plain , and application/octet-stream formatters.

We can add additional formatters to Restify by passing a hash of content type and parser when we create the server.

For example, we can write:

var restify = require('restify');
const util = require('util');

function respond(req, res, next) {
  res.send('hello');
  next();
}

var server = restify.createServer({
  formatters: {
    ['application/foo'](req, res, body) {
      if (body instanceof Error)
        return body.stack;

      if (Buffer.isBuffer(body))
        return body.toString('base64');

      return util.inspect(body);
    }
  }
});

server.get('/hello', respond);
server.head('/hello', respond);

server.listen(8080);

We have the formatters object for the application/foo content type.

Then we return a base64 string if the body is a buffer.

We can also add a q-value to our formatter definition to change the priority of the formatter:

var restify = require('restify');
const util = require('util');
function respond(req, res, next) {
  res.send('hello');
  next();
}
var server = restify.createServer({
  formatters: {
    ['application/foo q=0.9'](req, res, body) {
      if (body instanceof Error)
        return body.stack;
      if (Buffer.isBuffer(body))
        return body.toString('base64');
      return util.inspect(body);
    }
  }
});
server.get('/hello', respond);
server.head('/hello', respond);
server.listen(8080);

Restify ships with default formatters.

It can be overridden when passing formatter options with createServer :

var restify = require('restify');

function respond(req, res, next) {
  const body = 'hello world';
  res.writeHead(200, {
    'Content-Length': Buffer.byteLength(body),
    'Content-Type': 'text/plain'
  });
  res.write(body);
  res.end();
}
var server = restify.createServer();
server.get('/hello', respond);
server.head('/hello', respond);
server.listen(8080);

We called the writeHead method with an object with the content-type options we want to return in the header.

Error Handling

We can handle error conditions by passing an error object with the next function.

For example, we can write:

var restify = require('restify');
var errors = require('restify-errors');

var server = restify.createServer();

server.get('/hello/:foo', function(req, res, next) {
  var err = new errors.NotFoundError('not found');
  return next(err);
});

server.on('NotFound', function (req, res, err, cb) {
  return cb();
});

server.listen(8080);

We have the NotFound handler to do logging or collect data.

The NotFoundError constructor is in the restify-errors module.

We shouldn’t call res.send in the NotFound handler since it’s in a different error context.

Conclusion

We can format the our data and do content negotiating the same way.

Also, we create error objects with the restify-errors module.

Categories
Restify

Restify — Versioned Routes and Upgrade Requests

Restify is a simple Node back end framework.

In this article, we’ll look at how to add routes with Restify.

Versioned Routes

Restify routes can be versioned.

The version number should follow semantic versioning.

For example, we can write:

var restify = require('restify');

var server = restify.createServer();

function sendV1(req, res, next) {
  res.send(`hello: ${req.params.name}`);
  return next();
}

function sendV2(req, res, next) {
  res.send({ hello: req.params.name });
  return next();
}

server.get('/hello/:name', restify.plugins.conditionalHandler([
  { version: '1.1.3', handler: sendV1 },
  { version: '2.0.0', handler: sendV2 }
]));

server.listen(8080);

to have different versions of a route.

Then we can make a request with the accept-version header with the version number as a value.

If this header isn’t provided, then the handler for the latest version will be run.

For example, if we run:

curl -s localhost:8080/hello/foo

We get:

{
    "hello": "foo"
}

And if we run:

curl -s -H 'accept-version: ~1' localhost:8080/hello/foo

We get:

"hello: foo"

And if we run:

curl -s -H 'accept-version: ~2' localhost:8080/hello/foo

We get:

{
    "hello": "foo"
}

If we have an invalid version number in our header like:

curl -s -H 'accept-version: ~3' localhost:8080/hello/foo

We get:

{
    "code": "InvalidVersion",
    "message": "~3 is not supported by GET /hello/foo"
}

We can use the same route handlers for multiple versions by passing in an array:

var restify = require('restify');

var server = restify.createServer();

function sendV1(req, res, next) {
  res.send(`hello: ${req.params.name}`);
  return next();
}

function sendV2(req, res, next) {
  res.send({ hello: req.params.name });
  return next();
}

server.get('/hello/:name', restify.plugins.conditionalHandler([
  { version: '1.1.3', handler: sendV1 },
  { version: ['2.0.0', '2.1.0', '2.2.0'], handler: sendV2 }
]));

server.listen(8080);

Also, we can get the original requested version with the req.version method.

And we can get the version that’s matched by Restify with the matchedVersion method.

For example, we can write:

var restify = require('restify');

var server = restify.createServer();

server.get('/hello/:name', restify.plugins.conditionalHandler([
 {
    version: ['2.0.0', '2.1.0', '2.2.0'],
    handler: function (req, res, next) {
      res.send(200, {
        requestedVersion: req.version(),
        matchedVersion: req.matchedVersion()
      });
      return next();
    }
  }
]));

server.listen(8080);

Then if we run:

curl -s -H 'accept-version: ~2' localhost:8080/hello/foo

We get:

{
    "requestedVersion": "~2",
    "matchedVersion": "2.2.0"
}

Upgrade Requests

If an incoming HTTP requests that has the Connection: Upgrade header, then it’s treated differently by the Node server.

It won’t be pushed through to Restify by default.

We can make this pushed through with the handleUpgrades option.

For example, we can write:

var restify = require('restify');
var Watershed = require('Watershed');

var server = restify.createServer();

var ws = new Watershed();
server.get('/websocket/attach', function(req, res, next) {
  if (!res.claimUpgrade) {
    next(new Error('Connection Must Upgrade For WebSockets'));
    return;
  }

var upgrade = res.claimUpgrade();
  var shed = ws.accept(req, upgrade.socket, upgrade.head);
  shed.on('text', function(msg) {
    console.log('Received message from websocket client: ' + msg);
  });
  shed.send('hello there!');

  next(false);
});

server.listen(8080);

We create the Watershed instance to listen for WebSockets connection.

Conclusion

We can version our routes with Restify.

Also, we can use Watershed to handle Webscoket requests.

Categories
Restify

Restify — Routing

Restify is a simple Node back end framework.

In this article, we’ll look at how to add routes with Restify.

The next Function

The next function is called to call the next handler in the chain.

For example, we can write:

var restify = require('restify');

function respond(req, res, next) {
  res.send('hello ' + req.params.name);
  next();
}

var server = restify.createServer();

server.use([
  function(req, res, next) {
    if (Math.random() < 0.5) {
      res.send('done!');
      return next(false);
    }
    return next();
  }
]);

server.get('/hello/:name', respond);
server.head('/hello/:name', respond);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

Then if Math.random returns a number less than 0.5, then we get that the response is 'done' .

Otherwise, then routes are going to be called.

next also accepts an object that’s where instanceof Error is true .

For instance, we can write:

var restify = require('restify');

function respond(req, res, next) {
  res.send('hello ' + req.params.name);
  next();
}

var server = restify.createServer();

server.use([
  function(req, res, next) {
    return next(new Error('error'));
  }
]);

server.get('/hello/:name', respond);
server.head('/hello/:name', respond);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

Then we’ll see the error response since we passed in an Error instance with the message 'error' .

We can also call res.send with an error object.

We can write:

var restify = require('restify');

function respond(req, res, next) {
  res.send(new Error('error'));
  return next();
}

var server = restify.createServer();

server.get('/hello/:name', respond);
server.head('/hello/:name', respond);

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

to send an error response.

Routing

The routing logic for Restidy is similar to Express.

HTTP verbs are used with the parameterized resource to determine which handler to run.

The values associated with named placeholders are in req.params .

For example, we can write:

var restify = require('restify');

function send(req, res, next) {
  res.send('hello ' + req.params.name);
  return next();
}

var server = restify.createServer();

server.post('/hello', function(req, res, next) {
  res.send(201, Math.random().toString(36).substr(3, 8));
  return next();
});
server.put('/hello', send);
server.get('/hello/:name', send);
server.head('/hello/:name', send);
server.del('/hello/:name', function(req, res, next) {
  res.send(204);
  return next();
});

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

We have multiple routes in our Restify app.

get lets us handle GET requests.

post lets us handle POST requests.

put lets us handle PUT requests.

del lets us handle DELETE requests.

Other verbs that can be specified include opts and patch .

Hypermedia

We can render results of other routes with the server.route.render method.

For example, we can write:

var restify = require('restify');

function send(req, res, next) {
  res.send('hello ' + req.params.name);
  return next();
}

var server = restify.createServer();

server.get({name: 'city', path: '/cities/:slug'}, function(req, res, next) {
  res.send(req.params.slug);
  return next();
});

server.get('/hello', function(req, res, next) {
  res.send({
    country: 'Australia',
    capital: server.router.render('city', {slug: 'canberra'}, {details: true})
  });
  return next();
});

server.listen(8080, function() {
  console.log('%s listening at %s', server.name, server.url);
});

Then when we go to http://localhost:8080/hello , we get:

{
  "country": "Australia",
  "capital": "/cities/canberra?details=true"
}

The path and query string parameters will be URL encoded properly.

Conclusion

We can create routes with various server methods with Restify.

Categories
JavaScript Basics

Spreading Arrays with JavaScript

The spread operator is one of the best recent JavaScript features.

In this article, we’ll look at what we can do with the spread operator.

Spread Operator

Rhe spread syntax lets us expand iterable objects like arrays and strings in various situations.

Merging Arrays

We can merge arrays with it.

For example, we can write:

const a = [1, 2, 3];
const b = [4, 5, 6];
const c = [...a, ...b];

We put all the items from a and b into a new array and assign it to c .

So c is:

[1, 2, 3, 4, 5, 6]

Clone Array

Another way we can use it is to shallow copy an array.

For example, we can write:

const arr = [1, 2, 3];
const arrCopy = [...arr];

We made a copy with the spread operator. It expands the arr entries into a new array.

So arrCopy is also [1, 2, 3] .

But they reference different objects in memory.

Arguments to Array

The arguments object is an iterable object that has the arguments passed into a JavaScript function.

It’s only available in a traditional function.

For instance, we can write:

function foo() {
  const args = [...arguments];
  console.log(args);
}

args is an array.

So if we have:

foo(1, 2, 3)

Then args is [1, 2, 3] .

A more modern alternative that works with all functions is the rest operator:

const foo = (...args) => {
  console.log(args);
}

Then if we call it the same way:

foo(1, 2, 3)

args would have the same value.

String to Array

We can convert a string into an array of characters with the spread operator since string are iterable.

For example, we can write:

const string = 'hello world';
const array = [...string];

Then array is:

["h", "e", "l", "l", "o", " ", "w", "o", "r", "l", "d"]

Set to Array

A set is an iterable object that can’t have duplicate entries.

Since it’s iterable, we can use the spread operator with it.

For example, we can write:

const set = new Set([1, 2, 2, 3, 3]);
const arr = [...set];

Then arr is [1, 2, 3] since the duplicates are removed from the set.

Map to Array

Maps are key-value pairs that’s stored in an object.

It’s also iterable so we can use the spread operator with it.

For example, we can write:

const map = new Map([
  ['foo', 1],
  ['bar', 2]
]);
const arr = [...map];

We spread the map with the operator and arr would be:

[
  ['foo', 1],
  ['bar', 2]
]

Node List to Array

Node lists are a list of DOM nodes.

It’s an iterable object but it’s not an array.

To convert them to an array, we can write:

const nodeList = document.querySelectorAll('div');
const nodeArr = [...nodeList];

We convert the node list returned from querySelectorAll to an array with the spread operator.

Conclusion

We can use the spread operator to spread or copy arrays and convert iterable objects to arrays.

Categories
JavaScript Basics

How to Append Items to a JavaScript Array?

There are a few ways to append items to an array in JavaScript.

In this article, we’ll look at how to do that in different ways.

Array.prototype.push

The push method appends one more item into an array.

We can use it by writing:

arr.push('foo');

to add 'foo' to arr .

Also, we can add multiple elements to an array with it:”

arr.push('foo', 'bar');

Then both 'foo' and 'bar' are added to arr in the same order.

This also means we can use the spread operator to append then entries of an array with push :

arr.push(...['foo', 'bar']);

That does the same thing as the previous example.

Spread Operator

We can use the spread operator to create a new array with new entries added to it.

For instance, we can write:

arr = [...arr, 'foo', 'bar'];

We used the spread operator to spread the existing entries of arr into a new array.

Then we added 'foo' and 'bar' to it at the end.

Then we assigned the new array back to arr .

Array.prototype.splice

Also, we can use the splice method fo the array instance to append to an array.

It takes 3 or more arguments, which are the start index, deletes count, and items, we want to add.

To add items into an array, we can write:

arr.splice(arr.length, 0. 'foo', 'bar');

In the code above, we indicated that we want to start at the last index of the array + 1 with arr.length .

0 means we remove nothing.

The 'foo' and 'bar' are the items we add.

Set an Item to the length Index

JavaScript arrays aren’t fixed length, so we can assign an entry to any index.

If we want to append an entry to an array, then we can set it using the bracket notation as follows:

arr[arr.length] = 'foo';

Then we can append 'foo' to arr since arr.length is one index beyond the end index of the original array.

Array.prototype.concat

We can use the concat method of the array instance to append one or more entries into the array.

For instance, we can write:

arr = arr.concat('foo', 'bar');

Also, we can write:

arr = arr.concat(...['foo', 'bar']);

Either way, we pass in one or more arguments for the items we want to append to the new array in addition to the existing ones.

concat returns a new array, so we’ve to assign it to a variable to access it.

Mutate or Not

It’s better that we don’t mutate the array so that we keep the existing value.

It’s easier to test and track immutable data.

Therefore, it’s preferred to not mutate data if possible.

Conclusion

In JavaScript, there are many ways to append an item to an array.

We can use the spread operator, or we can use various array methods to do the same thing.

Some operators or methods mutate and some don’t, so we’ve to be careful when using them.