The New Relic Node.js agent can automatically instrument your Express.js application for detailed performance monitoring.
Let’s see it in action.
Imagine you have a simple Express app:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
To instrument this with New Relic, you first need to install the agent:
npm install newrelic --save
Then, you create a newrelic.js configuration file in the root of your project. A minimal configuration looks like this:
exports.config = {
app_name: 'My Express App',
license_key: 'YOUR_LICENSE_KEY'
};
Replace YOUR_LICENSE_KEY with your actual New Relic license key.
Now, the crucial step: you need to start your Node.js application with the New Relic agent. This is typically done by prepending newrelic to your node command:
newrelic node index.js
When you run this command, the New Relic agent is loaded before your application code. It hooks into Node.js’s internal mechanisms and Express.js’s event system.
Once your application is running and receiving traffic, you’ll start seeing data in your New Relic account. In the APM section for "My Express App," you’ll find:
- Transactions: Each incoming HTTP request to your Express app will be listed as a transaction. You’ll see details like the HTTP method and path (e.g.,
GET /). - Throughput: The rate at which transactions are being processed.
- Response Time: The average time it takes for your application to respond to requests.
- Error Rate: The percentage of requests that result in an unhandled error.
Drilling into a specific transaction, say GET /, will show you a Transaction Trace. This trace breaks down the request’s lifecycle, highlighting time spent in different parts of your code, including:
- Express Middleware: Time spent in each middleware function you’ve defined.
- Route Handlers: Time spent executing the logic within your route handlers.
- Database Queries: If you’re using a New Relic-supported database library (like
pgormysql2), the agent will automatically instrument those calls and show you query times. - External Services: Calls to external APIs will be traced.
The agent achieves this by using Node.js’s built-in module. _load hook and by patching core Node.js modules and popular libraries like Express. When newrelic is executed, it intercepts calls to require() and can inject its own logic before loading the requested module. For Express, it specifically looks for the express() function and instruments the returned application object’s methods like get, post, use, etc., to wrap your route handlers and middleware in its own timing and error-capturing logic.
The real power comes from understanding how the agent maps your code to these visible metrics. It doesn’t just time the entire request; it intelligently identifies segments within the request lifecycle. For instance, when it sees app.use(myMiddleware), it wraps myMiddleware so that its execution time is accounted for separately. Similarly, when it sees app.get('/users/:id', (req, res) => { ... }), it wraps the anonymous function you provide as the route handler. This granular breakdown is what allows you to pinpoint performance bottlenecks.
The agent can also automatically identify and instrument asynchronous operations. If your route handler calls a function that returns a Promise, or uses async/await, the New Relic agent is designed to correctly attribute the time spent waiting for those operations to complete back to the original transaction. This includes things like setTimeout, setInterval, and I/O operations handled by Node.js’s event loop.
What most people miss is that the newrelic command-line wrapper is not just a convenience; it’s the primary mechanism by which the agent ensures it’s loaded early enough to hook into the module loading process. If you were to require('newrelic') directly within your application code, it would likely be too late to instrument all the necessary core modules and libraries effectively, leading to incomplete data.
The next step is to explore how to customize what gets reported, such as ignoring specific transactions or adding custom attributes for deeper segmentation.