Categories
Express JavaScript Nodejs

Easy Logging with the Morgan Express Middleware

Logging is an important part of any app. We want to know what activities are going on and look for information to fix problems when they arise.

In this article, we’ll look at how to use the morgan middleware for adding logging in Express apps.

Parameters

The morgan middleware lets us pass in 2 arguments. The first is the format and the second is the options object.

Format

The format is a string. For example, we can pass in 'tiny' to show minimal information.

Also, we can pass in a string with the fields we want to show lik1:

morgan(':method :url :status');

The format can also be a custom format function like the following:

const express = require('express');  
const bodyParser = require('body-parser');  
const morgan = require('morgan')  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
morgan((tokens, req, res) => {  
  return [  
    tokens.method(req, res),  
    tokens.url(req, res),  
    tokens.status(req, res),  
  ].join(' ')  
})
app.get('/', (req, res) => {  
  res.send('foo');  
});
app.listen(3000, () => console.log('server started'));

We get the same thing as:

morgan(':method :url :status');

with the function that’s passed into the morgan function’s returned value.

Options

morgan accepts several properties in the options object.

immediate

immediate logs on request instead of response. The data from the response won’t be logged.

skip

A function to determine when logging is skipped.

stream

Output stream for writing log lines, defaults to process.stdout .

Predefined Formats

combined

Standard Apache combined log output:

:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"

common

Standard Apache common log output:

:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]

dev

Logging for development use. The :status token will be colored red for server error code, yellow for client error codes, cyan for redirection codes and uncolored for other codes.

:method :url :status :response-time ms - :res[content-length]

short

Shorter than default and includes response time.

:remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms

tiny

Minimal output:

:method :url :status :res[content-length] - :response-time ms

Tokens

We can create new tokens to log the fields we want.

For example, we can write:

const express = require('express');  
const bodyParser = require('body-parser');  
const morgan = require('morgan')  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
morgan.token('type', (req, res) => req.headers['content-type'])
app.get('/', (req, res) => {  
  res.send('foo');  
});
app.listen(3000);

Then :type will log req.headers[‘content-type’] .

Below are tokens built into morgan :

  • :date[format] — date with format in UTC. Formats available include clf for common long format (e.g. 10/Oct/2000:13:55:36 +0000 ), iso for ISO 8601 format (e.g. 2000–10–10T13:55:36.000 ), web for RFC 1123 format (e.g. Tue, 10 Oct 2000 13:55:36 GMT )
  • :http-version — HTTP version of the request
  • :method — HTTP method of the request
  • :referreer — Referrer header of the request. This will use the standard misspelled Referer header if it exists, otherwise will log Referrer
  • :remote-addr — the remote address of the request. This will use req.ip . Otherwise the standard req.connection.remoteAddress value
  • :remote-user — the user authenticated part for basic auth
  • :req[header] — the given header of the request. If it’s not present, it’ll be logged as -
  • :res[header] — the given header of the response. If it’s not present, it’ll be logged as -
  • :response-time[digits] — the time between a request coming into morgan and when the response headers are written in milliseconds.
  • :status — the response status. If the request/response cycle completes before a response was sent to the client, then the status will be empty
  • :url — the URL of the request. This will use req.originalUrl if it exists otherwise req.url will be used.
  • :user-agent — the contents of the User-Agent header of the request

morgan.compile(format)

This method compiles a format string into a format function for use by morgan .

A format string is a string that presents a single log line and can utilize token syntax.

Tokens are referred to by :token-name . If a token accepts arguments, we can pass it into [] .

Examples

Simple Example

Simple use of morgan is something like the following:

const express = require('express');  
const bodyParser = require('body-parser');  
const morgan = require('morgan')  
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(morgan('combined'));
app.get('/', (req, res) => {  
  res.send('foo');  
});
app.listen(3000);

Then we get something like:

::ffff:172.18.0.1 - - [28/Dec/2019:01:04:22 +0000] "GET / HTTP/1.1" 304 - "https://repl.it/languages/express" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"

Writing Logs to a File

We can write logs to a file by setting thestream option of the option object and pass it into the second argument as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const morgan = require('morgan')  
const app = express();  
const fs = require('fs')  
const path = require('path')  
const appLogStream = fs.createWriteStream(path.join(__dirname, 'app.log'), { flags: 'a' })
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(morgan('combined', { stream: appLogStream}));
app.get('/', (req, res) => {  
  res.send('foo');  
});
app.listen(3000);

Then we get:

::ffff:172.18.0.1 - - [28/Dec/2019:01:06:44 +0000] "GET / HTTP/1.1" 304 - "https://repl.it/languages/express" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"

In app.log .

Rotating Logs

We can rotate between logs bu setting the interval property of the object.

For example, we can write:

const express = require('express');  
const bodyParser = require('body-parser');  
const morgan = require('morgan')  
const app = express();  
const fs = require('fs')  
const path = require('path')  
const accessLogStream = fs.createWriteStream(path.join(__dirname, 'app.log'), { flags: 'a' })
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.use(morgan('combined', {  
  interval: '7d',  
  stream: accessLogStream  
}));
app.get('/', (req, res) => {  
  res.send('foo');  
});
app.listen(3000);

to rotate logs every week.

Custom Log Entry Format

We can define our own token and record it as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const morgan = require('morgan')  
const app = express();  
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));  
app.use(morgan(':id :method :url :date[iso]'))
morgan.token('id', (req) => req.id);app.get('/', (req, res) => {  
  req.id = 1;  
  res.send('foo');  
});
app.listen(3000);

Then since we set req.id to 1, we get:

1 GET / 2019-12-28T01:11:07.646Z

1 is in the first column since we specified :id first in the format string before :method .

Conclusion

morgan is an easy to use logger for Express apps. It’s available as a middleware and we can specify our log format and data to log in each entry with tokens.

To log what we want that’s included in the preset tokens, we can define our own tokens using the token method.

Categories
Express JavaScript

Guide to the Express Response Object — More Ways to Send

The Express response object lets us send a response to the client.

Various kinds of responses like strings, JSON, and files can be sent. Also, we can send various headers and status code to the client in addition to the body.

In this article, we’ll look at how to set headers and send status codes with other items, including the set method for setting headers, status for setting response status code, type for setting the Content-Type header value, and the vary method for setting the Vary header value.

Methods

res.set(field [, value])

We can use the set method to set response headers before sending a response.

For example, we can use it as follows:

const express = require('express');  
const path = require('path');  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res, next) => {  
  res.set({  
    'Content-Type': 'text/plain',  
    'Content-Length': '100'  
  })  
    .send();  
})  
app.listen(3000);

Then when we make a request to / , we get that the Content-Length header is set to 100 and the Content-Type is set to text/plain .

Note that we have to call send to send the response.

We can verify this with an HTTP client like Postman.

res.status(code)

We can call the status method to add a status code before sending the response.

For example, we can use it as follows:

const express = require('express');  
const path = require('path');  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res, next) => {  
  res.status(400).send('Bad Request');  
})
app.listen(3000);

Then we get Bad Request displayed and the 400 response code.

res.type(type)

The type method sets the Content-Type HTTP header to the MIME type determined by the mime.lookup() method from the node-mime package for the specified type.

If type contains the / character, then it sets the Content-Type to type .

This method doesn’t send the response.

For example, we can use it as follows:

const express = require('express');  
const path = require('path');  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.type('.html').send();  
})  
app.listen(3000);

Then we get that the Content-Type response header has the value text/html; charset=utf-8 .

If we have:

const express = require('express');  
const path = require('path');  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.type('.png').send();  
})  
app.listen(3000);

Then we get image/png instead of text/html for the value of Content-Type .

res.vary(field)

The vary method adds the field to the Vary response header if it’s not already there.

For example, we can use it as follows:

const express = require('express');  
const path = require('path');  
const app = express()  
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.vary('User-Agent').send();  
})  
app.listen(3000);

Then we have the Vary response header set to User-Agent .

The Vary header lets us to content negotiation, which is a mechanism for serving different kinds of data with the same URL.

The user agent can specify what’s best for the server.

The Vary header indicates which headers it’s used for content negotiation.

Since we have the Vary header set to User-Agent , our app uses the User-Agent header to serve different kinds of content according to the user agent of the browser.

Conclusion

The set method lets us set headers before sending our response. It takes an object with the response headers that we want to send.

status method is for setting a response status code before sending it. We can chain it with the response body.

The type method is for setting the Content-Type header value. The returned MIME-type is determined by the mime.lookup() method from node-mime .

The vary method for setting the Vary header value. The Vary header is used for content negotiation, which is serving different content according to the field specified by the Vary header’s value with the same URL.

Categories
Express JavaScript Nodejs

Add Timeout Capability to Express Apps with connect-timeout

To prevent requests from taking too long, it’s a good idea to have a maximum processing time for requests.

We can do this by making requests that take too long to time out. The connect-timeout middleware can help us do this.

In this article, we’ll look at how to use it to implement time out features in our app.

One Catch

This middleware sends a timeout response back to the client if a request exceeds the maximum time that we specify.

However, in the background, it’ll continue to run whatever was running before the request timed out.

Resources like CPU and memory will continue to be used until the process terminates.

We may need to end the process and free up any resources that it was using.

Usage

The timeout function takes 2 arguments. The first is the time for the maximum time for the request to process, and the second is an options object.

Time

The time is either a number in milliseconds or a string accepted by the ms module.

On timeout, req will emit timeout .

Options

The options object takes an optional object that can have the following property:

  • respond — controls if this module will response in the form of forwarding an error. If it’s true , the timeout error is passed to the next so that we may customize the response behavior. The error has a timeout property and status code 503. The default is true .

req.clearTimeout()

The clearTimeout method clears the timeout on the request and it won’t fire for this request in the future.

req.timedout

A boolean that is true if timeout is fired and false otherwise.

Example

We can use it as follows as top-level middleware. We have to stop the flow to the next middleware if the request timed out so they won’t run.

For example, we can write:

const express = require('express');  
const bodyParser = require('body-parser');  
const timeout = require('connect-timeout');  
const app = express();  
const haltOnTimedout = (req, res, next) => {  
  if (!req.timedout) {  
    next();  
  }  
}  
app.use(timeout('5s'))  
app.use(bodyParser.json({ extended: true }))  
app.use(haltOnTimedout)
app.get('/', (req, res) => {  
  res.send('success');  
})
app.listen(3000);

Then our endpoints will time out after 5 seconds and only allow to proceed beyond the bodyParser middleware if the request hasn’t timed out.

We can check time out by adding setTimeout to our route handler as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const timeout = require('connect-timeout');  
const app = express();  
const haltOnTimedout = (req, res, next) => {  
  if (!req.timedout) {  
    next();  
  }  
}  
app.use(timeout('5s'))  
app.use(bodyParser.json({ extended: true }))  
app.use(haltOnTimedout)
app.get('/', (req, res) => {  
  setTimeout(() => {  
    res.send('success');  
  }, Math.random() * 7000);  
})
app.listen(3000);

The some requests will time out and get us a 503 response.

We can catch the error and handle it ourselves as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const timeout = require('connect-timeout');  
const app = express();  
const haltOnTimedout = (req, res, next) => {  
  if (!req.timedout) {  
    next();  
  }  
}
app.use(timeout('5s'))  
app.use(bodyParser.json({ extended: true }))  
app.use(haltOnTimedout)
app.get('/', (req, res, next) => {  
  setTimeout(() => {  
    if (req.timedout) {  
      next();  
    }  
    else {  
      res.send('success');  
    }  
  }, Math.random() * 7000);  
})
app.use((err, req, res, next) => {  
  res.send('timed out');  
})
app.listen(3000);

In the setTimeout callback, we check the req.timedout property and called next() if it times out so that it’ll go to our error handler.

Note that we have the error handler after our route so calling next will go to our error handler.

We should get timed out if our request timed out instead of the error message and stack trace.

Conclusion

We can use the connect-timeout middleware to implement time out functionality for our routes.

This sends a 503 response if a request times out, but it doesn’t clear all the resources that’s being used for the request and end the process that’s still running.

We can check if a request has timed out with the req.timedout property.

Finally, we can catch the error by adding our own error handler after our routes and calling next .

Categories
Express JavaScript Nodejs

Easy Debugging with the errorhandler Express Middleware

The errorhandler Express middleware can be used to handle errors during app development.

In this article, we’ll look at how to use it to debug our code during development.

Why do we Need This Middleware?

We need this for debugging purposes. It shows the full error stack trace and internals of any object that’s passed to this module will be sent back to the client if an error occurs.

It’ll display as many details as possible about errors. Object details will be fully displayed. The data can be displayed as HTML, JSON, and plain text.

When the object is a standard Error object, the string provided by the stack property will be returned in HTML or text responses.

When an object is a non-error object, the result of util.inspect will be returned in HTML or text format.

Error involving JSON responses will be an object with enumerable properties from the object in the response.

Installing the Package

We can install it by running:

npm install errorhandler

Options

The errorhandler middleware takes a few options to control what to return.

log

The log property lets us provide a function to be called with the error and a string representation of the error.

It can be used to write the error to any desired location or set to false to only send the error back in the response.

The function has the signature (err, str, req, res) where err is the Error object, str is a string representation of the error, req is the request object and res is the response object.

This function is run after the response has been written.

The default value for this option is true unless process.env.NODE_ENV has the value 'test' .

Other possible values include true , which logs errors using console.error(str) , or false , which only sends the error back in the response.

Examples

We can use it as follows:

const express = require('express');  
const express = require('express');  
const bodyParser = require('body-parser');  
const errorHandler = require('errorhandler');  
const app = express();
app.use(bodyParser.json());
app.get('/', (req, res, next) => {  
  throw new Error('error');  
  res.send();  
})
app.use(errorHandler());
app.listen(3000);

Since our route throws an error, we should get objects with the error string and object logged.

We should get something like::

Connect  
500 Error: error  
   at app.get (/home/runner/index.js:9:9)  
   at Layer.handle [as handle_request] (/home/runner/node_modules/express/lib/router/layer.js:95:5)  
   at next (/home/runner/node_modules/express/lib/router/route.js:137:13)  
   at Route.dispatch (/home/runner/node_modules/express/lib/router/route.js:112:3)  
   at Layer.handle [as handle_request] (/home/runner/node_modules/express/lib/router/layer.js:95:5)  
   at /home/runner/node_modules/express/lib/router/index.js:281:22  
   at Function.process_params (/home/runner/node_modules/express/lib/router/index.js:335:12)  
   at next (/home/runner/node_modules/express/lib/router/index.js:275:10)  
   at jsonParser (/home/runner/node_modules/body-parser/lib/types/json.js:110:7)  
   at Layer.handle [as handle_request] (/home/runner/node_modules/express/lib/router/layer.js:95:5)

Displayed on our screen.

We can add our own custom logging function as follows:

const express = require('express');  
const bodyParser = require('body-parser');  
const errorHandler = require('errorhandler');  
const app = express();  
const errorNotification = (err, str, req) => {  
  console.log(str);  
}
app.use(bodyParser.json());
app.get('/', (req, res, next) => {  
  throw new Error('error');  
  res.send();  
})
app.use(errorHandler({ log: errorNotification }));
app.listen(3000);

In the code above, we added our own errorNotification function. All we did was logging the error string with our errorNotification handler function.

Then we should get:

Error: error  
    at app.get (/home/runner/index.js:12:9)  
    at Layer.handle [as handle_request] (/home/runner/node_modules/express/lib/router/layer.js:95:5)  
    at next (/home/runner/node_modules/express/lib/router/route.js:137:13)  
    at Route.dispatch (/home/runner/node_modules/express/lib/router/route.js:112:3)  
    at Layer.handle [as handle_request] (/home/runner/node_modules/express/lib/router/layer.js:95:5)  
    at /home/runner/node_modules/express/lib/router/index.js:281:22  
    at Function.process_params (/home/runner/node_modules/express/lib/router/index.js:335:12)  
    at next (/home/runner/node_modules/express/lib/router/index.js:275:10)  
    at jsonParser (/home/runner/node_modules/body-parser/lib/types/json.js:110:7)  
    at Layer.handle [as handle_request] (/home/runner/node_modules/express/lib/router/layer.js:95:5)

From the console.log output in addition to what we see on the page.

Conclusion

With the errorhandler middleware, we can debug our Express apps more easily because we can see more information about the error like the stack trace and the object with more error information.

Also, we can see the request and response object when the error occurred.

The error data can be displayed in HTML, JSON or plain text.

We can add it by including the middleware with use , but we have to be careful not run use this in production since it shows internal data about the error.

Categories
Express JavaScript

Guide to the Express Response Object — Files and JSON

The Express response object lets us send a response to the client.

Various kinds of responses like strings, JSON, and files can be sent. Also, we can send various headers and status code to the client in addition to the body.

In this article, we’ll look at various properties of the response object, including files and JSON responses.

Methods

res.download

res.download sends a file response to the server.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.download('./public/foo.txt');  
})
app.listen(3000, () => console.log('server started'));

Then when a request is made to this route, then we’ll get a file downloaded.

We can save a file with a different than the file on the server by writing:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res.download('./files/foo.txt', 'foobar.txt');  
});
app.listen(3000, () => console.log('server started'));

The file foo.txt from the server will be saved as foobar.txt by the client.

The download method also takes an error handler in case the download response fails.

For example, we can handle errors with the built-in error handler as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res, next) => {  
  res.download('./files/foo.txt', 'foobar.txt', err => next(err));  
});
app.listen(3000, () => console.log('server started'));

We called next with the error object passed in to pass it onto the error handler.

res.end([data] [, encoding])

res.end ends the response process. The method comes from response.end() of the http module.

We can use it to end the response without sending any data. To respond with data, we can use res.send() or other methods.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res.status(404).end();  
});
app.listen(3000, () => console.log('server started'));

Then we sent a 404 response back to the client with no data.

res.format(object)

The res.format method takes the Accept HTTP header from the request object and use res.accepts to select a handler for the request.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res.format({  
    'text/plain': () => {  
      res.send('hi');  
    }, 'text/html': () => {  
      res.send('<p>hi</p>');  
    }, 'application/json': () => {  
      res.json({ message: 'hi' });  
    }, 'default': () => {  
      res.status(406).end();  
    }  
  })  
});
app.listen(3000, () => console.log('server started'));

If we send a GET request to / with Accept header’s value set to text/plain , we get hi . If we send text/html as the Accept header’s value, we get <p>hi</p> , and so on.

default specifies the default response if none of the response types are matched.

It’ll work even if we abbreviate the MIME type names to something shorter:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res.format({  
    text: () => {  
      res.send('hi');  
    }, html: () => {  
      res.send('<p>hi</p>');  
    }, json: () => {  
      res.json({ message: 'hi' });  
    }, default: () => {  
      res.status(406).end();  
    }  
  })  
});
app.listen(3000, () => console.log('server started'));

Then we get the same thing as before.

res.get(field)

The get method returns the HTTP response header specified by field . The match isn’t case sensitive.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {    
  res.send(res.get('Content-Type'));  
});
app.listen(3000, () => console.log('server started'));

res.json([body])

res.json lets us send a JSON response to the client. The parameter can be any JSON type, including object, array, string, Boolean, number, or null.

For example, if we have:

const express = require('express')  
const app = express()
app.use(express.json())  
app.use(express.urlencoded({ extended: true }))
app.get('/', (req, res) => {  
  res.json({ message: 'hi' });  
})
app.listen(3000, () => console.log('server started'));

Then we get:

{"message":"hi"}

as the response.

res.jsonp([body])

We can use this to send a JSON response with JSONP support. It returns a callback with the JSON body passed in.

For example, we can use it as follows:

const express = require('express');  
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());  
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {  
  res.jsonp({ user: 'foo' });  
});
app.listen(3000, () => console.log('server started'));

If we send a GET request with ?callback=cb as the query string, then we get:

// typeof cb === 'function' && cb({"user":"foo"});

as the response. If we leave out the callback search parameter, then we get a normal JSON response:

{  
    "user": "foo"  
}

Conclusion

We can send various kinds of responses easily with the response object.

To send files, we can use download .

To send different things according to the Accept request header, we can use the format method.

Finally, to send JSON, we can use the json or jsonp methods depending on if we need to call a callback function on the client or not.