Send SMS Messages with Node.js, Express, and Plivo
This guide provides a step-by-step walkthrough for building a simple Node.js application using the Express framework to send SMS messages via the Plivo API. We'll cover project setup, API implementation, security considerations, error handling, deployment, and testing.
By the end of this tutorial, you'll have a functional Express API endpoint capable of accepting requests and sending SMS messages through your Plivo account. This serves as a foundation for integrating SMS capabilities – like notifications, alerts, or verification codes – into your applications.
Project Overview and Goals
Goal: To create a simple, secure, and robust Node.js Express API endpoint (POST /send-sms
) that accepts a recipient phone number and a message body, then uses the Plivo API to send the SMS.
Problem Solved: Provides a basic backend service to programmatically send SMS messages, abstracting the direct Plivo API interaction behind a simple web service.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its asynchronous, event-driven nature, well-suited for I/O operations like API calls.
- Express.js: A minimal and flexible Node.js web application framework. Chosen for its simplicity in setting up routes and handling HTTP requests.
- Plivo: A cloud communications platform providing SMS and Voice APIs. Chosen as the third-party service for dispatching SMS messages.
- Plivo Node.js SDK: Simplifies interaction with the Plivo REST API.
- dotenv: A module to load environment variables from a
.env
file intoprocess.env
. Essential for managing sensitive credentials securely.
System Architecture:
+-------------+ +-----------------------+ +-----------------+ +-----------------+
| | HTTP | | Plivo | | SMS | |
| User/Client | ----> | Node.js/Express App | ----> | Plivo API | ----> | Recipient Phone |
| (e.g. curl) | POST | (API Endpoint /send-sms)| SDK | (Sends Message) | | |
+-------------+ +-----------------------+ +-----------------+ +-----------------+
| Loads Credentials
| from .env
v
+---------+
| .env |
| (Secrets)|
+---------+
Prerequisites:
- Node.js and npm (or yarn): Installed on your development machine. Download from nodejs.org.
- Plivo Account: Sign up at plivo.com.
- Plivo Auth ID and Auth Token: Found on your Plivo account dashboard.
- Plivo Phone Number or Registered Sender ID:
- US/Canada: You must purchase an SMS-enabled Plivo phone number to send messages.
- Other Countries: You might be able to use a registered Alphanumeric Sender ID (check Plivo's country-specific guidelines and potentially register one via Plivo support). Using a Plivo number often works too.
- Basic understanding of JavaScript and REST APIs.
- (Optional)
curl
or Postman: For testing the API endpoint.
Trial Account Limitations: If using a Plivo trial account, you can typically only send messages to phone numbers verified within your Plivo console (Sandbox Numbers).
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.
mkdir plivo-node-sms-guide cd plivo-node-sms-guide
-
Initialize Node.js Project: This creates a
package.json
file to manage project dependencies and scripts.npm init -y
(The
-y
flag accepts default settings) -
Install Dependencies: We need Express for the web server, the Plivo SDK to interact with their API, and
dotenv
to manage environment variables.npm install express plivo dotenv
-
Create Project Structure: Set up a basic structure for clarity.
mkdir routes # To hold our API route definitions touch server.js .env .gitignore
server.js
: The main entry point for our application.routes/
: Directory to organize route handlers..env
: File to store sensitive credentials (API keys, etc.). Never commit this file to version control..gitignore
: Specifies intentionally untracked files that Git should ignore.
-
Configure
.gitignore
: Addnode_modules
and.env
to prevent committing them. Open.gitignore
and add:# .gitignore # Dependencies node_modules # Environment variables .env # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log*
-
Set up Basic Express Server (
server.js
): Openserver.js
and add the initial Express setup.// server.js require('dotenv').config(); // Load environment variables from .env file const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; // Use port from env var or default to 3000 // Middleware to parse JSON request bodies app.use(express.json()); // Middleware to parse URL-encoded request bodies app.use(express.urlencoded({ extended: true })); // Basic root route (optional) app.get('/', (req, res) => { res.send('Plivo SMS Sender API is running!'); }); // Placeholder for SMS routes (we'll add this soon) // const smsRoutes = require('./routes/sms'); // app.use('/api', smsRoutes); // Start the server app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
require('dotenv').config();
: Loads variables from.env
. Crucial to do this early.express.json()
andexpress.urlencoded()
: Middleware needed to parse incoming request bodies.process.env.PORT || 3000
: Allows configuring the port via environment variables, essential for deployment platforms.
2. Implementing Core Functionality (Sending SMS)
Now, let's create the logic to interact with the Plivo API using their Node.js SDK.
-
Create SMS Route File: Inside the
routes
directory, create a file namedsms.js
.touch routes/sms.js
-
Implement the Sending Logic (
routes/sms.js
): This file will define the router and the handler for our/send-sms
endpoint.// routes/sms.js const express = require('express'); const plivo = require('plivo'); const router = express.Router(); // Validate environment variables const AUTH_ID = process.env.PLIVO_AUTH_ID; const AUTH_TOKEN = process.env.PLIVO_AUTH_TOKEN; const SENDER_NUMBER = process.env.PLIVO_SENDER_NUMBER; // Your Plivo number or Sender ID if (!AUTH_ID || !AUTH_TOKEN || !SENDER_NUMBER) { console.error('Error: Plivo credentials or sender number not configured in .env file.'); // In a real app, you might prevent the server from starting or handle this more gracefully. // For this guide, we'll log the error and let requests fail if credentials aren't set. } // Initialize Plivo client (only if credentials exist) let client; if (AUTH_ID && AUTH_TOKEN) { client = new plivo.Client(AUTH_ID, AUTH_TOKEN); } // POST /api/send-sms router.post('/send-sms', async (req, res) => { // Basic input validation const { to, text } = req.body; if (!to || !text) { // Use single quotes for the outer string to allow standard double quotes inside return res.status(400).json({ success: false, error: 'Missing required fields: ""to"" and ""text"".' }); } // Basic E.164 format check (simple version) // A more robust validation library (like libphonenumber-js) is recommended for production, // as it handles varying international lengths, country codes, and number types more accurately. if (!/^\+\d{1,15}$/.test(to)) { return res.status(400).json({ success: false, // Use single quotes for the outer string error: 'Invalid ""to"" number format. Must be E.164 format (e.g., +14155552671).' }); } if (!client) { console.error('Plivo client not initialized due to missing credentials.'); return res.status(500).json({ success: false, error: 'Server configuration error: Plivo client not available.' }); } try { console.log(`Attempting to send SMS to ${to} from ${SENDER_NUMBER}`); const response = await client.messages.create({ src: SENDER_NUMBER, // Sender ID or Plivo Number from .env dst: to, // Destination number from request body text: text, // Message text from request body }); console.log('Plivo API Response:', response); // Plivo API generally returns a 202 Accepted on success // The response contains message UUIDs res.status(202).json({ success: true, message: 'SMS send request accepted by Plivo.', plivoResponse: response, }); } catch (error) { console.error('Error sending SMS via Plivo:', error); // Provide a more specific error message if possible const errorMessage = error.message || 'Failed to send SMS.'; const statusCode = error.statusCode || 500; // Use Plivo's status code if available res.status(statusCode).json({ success: false, error: 'Plivo API Error', details: errorMessage, plivoError: error // Include the raw error for debugging if appropriate }); } }); module.exports = router; // Export the router
- We import
express
andplivo
. - We create an
express.Router()
. - Crucially, we load Plivo credentials and the sender number from
process.env
. We add checks to ensure they exist. - The Plivo client is initialized only if credentials are present.
- The
POST /send-sms
handler usesasync/await
for cleaner asynchronous code. - Input Validation: Basic checks ensure
to
andtext
are provided andto
roughly matches E.164 format. A note about using better libraries likelibphonenumber-js
is added. - Plivo Call:
client.messages.create()
sends the SMS.src
,dst
, andtext
are the key parameters. - Response Handling: On success (Plivo returns 202 Accepted), we send a success JSON response. On error, we catch it, log it, and send an appropriate error JSON response including details from the Plivo error object.
- We import
-
Mount the Router in
server.js
: Uncomment and add the lines inserver.js
to use the router we just created.// server.js require('dotenv').config(); const express = require('express'); const smsRoutes = require('./routes/sms'); // Import the router const app = express(); const PORT = process.env.PORT || 3000; app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.get('/', (req, res) => { res.send('Plivo SMS Sender API is running!'); }); // Mount the SMS routes under the /api path app.use('/api', smsRoutes); // Use the imported router app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
Now, requests to
POST /api/send-sms
will be handled by ourroutes/sms.js
logic.
3. Building the API Layer
We've already implemented the core API endpoint (POST /api/send-sms
) in the previous step. This section details its usage and provides testing examples.
API Endpoint:
- Method:
POST
- Path:
/api/send-sms
- Request Body: JSON object
to
(string, required): The recipient's phone number in E.164 format (e.g.,+14155552671
).text
(string, required): The content of the SMS message.
- Authentication: None implemented at this layer (assumes the API is internal or protected by infrastructure like a firewall or API gateway). For public APIs, adding authentication (e.g., API keys, JWT) is strongly recommended (see Section 7).
- Request Validation: Checks for presence of
to
andtext
, and basic E.164 format forto
.
Example curl
Request:
Replace placeholders with your actual server address, recipient number, and message. Use single quotes around the JSON data block for shell compatibility.
curl -X POST http://localhost:3000/api/send-sms \
-H ""Content-Type: application/json"" \
-d '{
""to"": ""+14155552671"",
""text"": ""Hello from your Node.js Plivo App!""
}'
Example Success Response (HTTP 202 Accepted):
{
""success"": true,
""message"": ""SMS send request accepted by Plivo."",
""plivoResponse"": {
""message"": ""message(s) queued"",
""messageUuid"": [
""1f7a77ca-f1b7-11ee-9d7c-0242ac110003""
],
""apiId"": ""1f7a6efc-f1b7-11ee-9d7c-0242ac110003""
}
}
(Note: The exact structure of plivoResponse
might vary slightly)
Example Error Response (HTTP 400 Bad Request - Missing Field):
The JSON response body would look like this:
{
""success"": false,
""error"": ""Missing required fields: \""to\"" and \""text\"".""
}
Example Error Response (HTTP 400 Bad Request - Invalid Number Format):
The JSON response body would look like this:
{
""success"": false,
""error"": ""Invalid \""to\"" number format. Must be E.164 format (e.g., +14155552671).""
}
Example Error Response (HTTP 500 Internal Server Error - Plivo API Failure):
{
""success"": false,
""error"": ""Plivo API Error"",
""details"": ""Resource not found"", // Example error message from Plivo
""plivoError"": { /* Full Plivo error object might be included here */ }
}
4. Integrating with Plivo
Proper configuration and secure handling of credentials are key.
-
Obtain Plivo Credentials and Sender Number:
- Log in to your Plivo Console.
- Auth ID & Auth Token: Navigate to the main dashboard (usually the landing page after login). Your Auth ID and Auth Token are displayed prominently.
- Sender Number/ID:
- For US/Canada: Go to
Phone Numbers
->Buy Numbers
. Search for and purchase an SMS-enabled number in the desired country/region. Note down the full number in E.164 format (e.g.,+12025551234
). - For other regions: Check Plivo's documentation for sender ID requirements. You might use a Plivo number purchased as above, or you may need to register an Alphanumeric Sender ID via
Messaging
->Sender IDs
->Add Sender ID
(this often requires approval).
- For US/Canada: Go to
-
Configure Environment Variables (
.env
file): Open the.env
file in your project's root directory and add your credentials. Replace the placeholder values.# .env - Plivo Configuration # DO NOT COMMIT THIS FILE TO VERSION CONTROL # Plivo API Credentials (from Plivo Console Dashboard) PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID_HERE PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN_HERE # Plivo Sender Number or Registered Sender ID # For US/Canada, use the E.164 format number you purchased (e.g., +12025551234) # For other regions, use your registered Alphanumeric Sender ID or a Plivo number PLIVO_SENDER_NUMBER=YOUR_PLIVO_NUMBER_OR_SENDER_ID
PLIVO_AUTH_ID
: Your unique authentication identifier.PLIVO_AUTH_TOKEN
: Your secret authentication token. Treat this like a password.PLIVO_SENDER_NUMBER
: The number or ID messages will appear to come from. Must be a valid Plivo number (for US/Canada) or a registered Sender ID where applicable.
-
Load Environment Variables: The
require('dotenv').config();
line at the top ofserver.js
handles loading these variables intoprocess.env
, making them accessible throughout your application (as seen inroutes/sms.js
).
Security: The .env
file keeps your sensitive credentials out of your source code. Ensure your .gitignore
file includes .env
to prevent accidentally committing it. In production environments, these variables should be set directly in the deployment platform's configuration settings, not via a .env
file.
Fallback Mechanisms: For this simple guide, there's no fallback. In a production system requiring high availability, you might consider:
- Implementing retries (see next section).
- Having a secondary messaging provider configured and switching if Plivo experiences an outage (requires more complex logic).
5. Implementing Error Handling and Logging
Robust error handling and logging are crucial for debugging and reliability.
-
Error Handling Strategy:
- Validation Errors: Return HTTP 400 Bad Request for invalid client input (missing fields, bad number format). Provide clear JSON error messages.
- Configuration Errors: If Plivo credentials aren't set, log an error on startup and return HTTP 500 if an attempt is made to use the uninitialized client.
- Plivo API Errors: Catch errors from the
client.messages.create()
call. Log the detailed error. Return an appropriate HTTP status code (often 500 Internal Server Error, or useerror.statusCode
if provided by Plivo's SDK) and a JSON error message including details from the Plivo error. - Unexpected Errors: Use a global error handler in Express (more advanced, not shown here) or ensure all async routes have
try...catch
blocks to prevent unhandled promise rejections crashing the server.
-
Logging:
- Current: We are using
console.log
for successful operations and Plivo responses, andconsole.error
for errors. This is suitable for development. - Production: Use a dedicated logging library like Winston or Pino. These offer:
- Different log levels (debug, info, warn, error).
- Structured logging (JSON format is common for easier parsing).
- Multiple transports (log to console, files, external services).
- Example Logging Points:
- Log incoming request details (method, path, maybe sanitized body).
- Log attempt to send SMS (include
to
andfrom
numbers). - Log successful Plivo API response (including
messageUuid
). - Log detailed error information when Plivo API calls fail or other exceptions occur.
- Current: We are using
-
Retry Mechanisms:
- Sending an SMS is often idempotent from Plivo's side (sending the same request usually doesn't result in duplicate messages if the first was accepted), but network issues could prevent the request reaching Plivo.
- For critical messages, you could implement a simple retry strategy with exponential backoff for network errors or specific transient Plivo errors (e.g., 5xx status codes). Libraries like
async-retry
can help. - Example (Conceptual - not added to code):
// Conceptual retry logic using async-retry const retry = require('async-retry'); // ... inside the route handler ... try { await retry(async bail => { // bail is a function to stop retrying for non-recoverable errors try { const response = await client.messages.create(/* ... */); console.log('Plivo API Response:', response); // If successful, return response or true return response; } catch (error) { // Check if error is potentially transient (e.g., network, 5xx) // Note: While >= 500 is a common check, some 5xx might not be retryable (e.g., Plivo config error). // Use 'bail' for specific non-retryable status codes or error types. if (error.statusCode >= 500 || error.code === 'ENETUNREACH' /* etc */) { console.warn(`Retrying Plivo request due to error: ${error.message}`); throw error; // Throw error to trigger retry } else { // Don't retry for permanent errors (e.g., bad request, auth failure) bail(new Error(`Non-retryable Plivo error: ${error.message}`)); } } }, { retries: 3, // Number of retries factor: 2, // Exponential backoff factor minTimeout: 1000, // Initial timeout in ms }); // Handle successful response after retries... res.status(202).json(/* ... */); } catch (error) { // Handle final error after all retries failed... console.error('Error sending SMS via Plivo after retries:', error); res.status(error.statusCode || 500).json(/* ... */); }
- Caution: Be careful not to retry indefinitely or for errors that won't resolve (like invalid credentials or invalid recipient numbers).
-
Testing Error Scenarios:
- Send requests missing
to
ortext
. - Send requests with invalid
to
number formats. - Temporarily modify
.env
with incorrectPLIVO_AUTH_ID
orPLIVO_AUTH_TOKEN
to test authentication failures. - Temporarily modify
PLIVO_SENDER_NUMBER
to an invalid one. - (If possible) Test with a recipient number known to cause issues (e.g., landline, blocked number - consult Plivo docs for test numbers if available).
- Simulate network issues if testing infrastructure allows.
- Send requests missing
6. Database Schema and Data Layer
For this specific guide (only sending an SMS via an API call), a database is not strictly required.
However, in a real-world application, you might add a database (like PostgreSQL, MySQL, MongoDB) to:
- Log Sent Messages: Store details like
messageUuid
, recipient, sender, timestamp, status (initially 'queued', potentially updated via webhooks), and message content. This aids auditing and tracking. - Manage Users/Contacts: Store recipient information if sending to registered users.
- Queue Messages: Store messages to be sent asynchronously by a background worker, especially for bulk sending or to decouple the API response from the actual sending process.
If adding a database:
- Schema: A simple
messages
table might include:id
(PK),plivo_message_uuid
(unique),recipient_number
,sender_id
,message_text
,status
,created_at
,updated_at
. - Data Layer: Use an ORM (like Sequelize for SQL, Mongoose for MongoDB) or a query builder (Knex.js) to interact with the database.
- Migrations: Use tools like
sequelize-cli
orknex-migrations
to manage database schema changes.
(Implementation details omitted as they are beyond the scope of this basic SMS sending guide).
7. Adding Security Features
Enhancing the security of your API endpoint is essential.
-
Input Validation and Sanitization:
- Validation: We already implemented basic validation for
to
andtext
. For production, use a robust validation library likejoi
orexpress-validator
to define schemas for request bodies. - Sanitization: Since the input
text
is directly passed to the Plivo API for SMS encoding and delivery, and not rendered in HTML or executed within our application, the risk of XSS from this specific input is low within our service. However, always sanitize input if you were to store it and display it elsewhere. Ensure theto
number format is strictly validated (E.164).
- Validation: We already implemented basic validation for
-
Common Vulnerabilities & Protections:
- Authentication/Authorization: This guide omits direct API authentication, assuming protection via infrastructure. For many real-world uses, especially if the API isn't purely internal, implement authentication. Simple methods include checking for a secret API key in headers (
X-API-Key
) or using more robust methods like JWT. - Rate Limiting: Protect against brute-force attacks or abuse where users flood the API with requests.
- Security Headers: Use headers like
X-Content-Type-Options
,Referrer-Policy
,Strict-Transport-Security
,X-Frame-Options
to mitigate common web vulnerabilities.
- Authentication/Authorization: This guide omits direct API authentication, assuming protection via infrastructure. For many real-world uses, especially if the API isn't purely internal, implement authentication. Simple methods include checking for a secret API key in headers (
-
Implement Rate Limiting: Use a library like
express-rate-limit
.- Install:
npm install express-rate-limit
- Apply in
server.js
:Adjust// server.js require('dotenv').config(); const express = require('express'); const rateLimit = require('express-rate-limit'); // Import const smsRoutes = require('./routes/sms'); const app = express(); const PORT = process.env.PORT || 3000; // Apply rate limiting to API routes const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again after 15 minutes', standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); app.use('/api', apiLimiter); // Apply the limiter to /api routes app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Basic root route app.get('/', (req, res) => { res.send('Plivo SMS Sender API is running!'); }); app.use('/api', smsRoutes); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
windowMs
andmax
according to your expected usage and security requirements.
- Install:
-
Implement Basic Security Headers: Use the
helmet
middleware.- Install:
npm install helmet
- Apply in
server.js
:// server.js require('dotenv').config(); const express = require('express'); const rateLimit = require('express-rate-limit'); const helmet = require('helmet'); // Import const smsRoutes = require('./routes/sms'); const app = express(); const PORT = process.env.PORT || 3000; app.use(helmet()); // Use Helmet for basic security headers const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again after 15 minutes', standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); app.use('/api', apiLimiter); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Basic root route app.get('/', (req, res) => { res.send('Plivo SMS Sender API is running!'); }); app.use('/api', smsRoutes); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
helmet()
applies several default security-related HTTP headers.
- Install:
-
Protecting API Keys: Already covered by using
.env
and.gitignore
. Ensure production environments use secure configuration management. -
Testing for Vulnerabilities:
- Use security scanning tools (like OWASP ZAP, Burp Suite) against your deployed application (in a test environment).
- Perform manual penetration testing focusing on input validation, authentication/authorization (if added), and potential injection points.
- Check dependency vulnerabilities regularly (
npm audit
).
8. Handling Special Cases Relevant to SMS
- E.164 Format: Strictly enforce the
+
prefix and country code format forto
numbers. Plivo requires this. Considerlibphonenumber-js
for robust parsing and validation, as it understands nuances like variable number lengths per country. - Character Encoding (GSM vs. Unicode):
- Standard SMS messages use the GSM 03.38 character set (160 characters per segment).
- Including characters outside this set (like many emojis or non-Latin characters) forces Unicode encoding (UCS-2), reducing the segment length to 70 characters.
- Plivo handles this automatically based on the
text
content. Be aware that longer messages or messages with Unicode characters will be split into multiple segments (concatenated on the recipient's device) and billed accordingly by Plivo. See Plivo's Encoding and Concatenation guide.
- Sender ID Rules: Reiterate that US/Canada requires a Plivo number. Other countries have varying rules for numeric vs. alphanumeric sender IDs, often requiring pre-registration. Using an invalid or unregistered sender ID will cause message delivery failure. Check Plivo's country-specific SMS guidelines.
- Trial Account Limitations: Sending is restricted to verified ""Sandbox"" numbers.
- Message Length: While Plivo handles concatenation, be mindful of costs associated with multi-segment messages. Very long texts might be better suited for other channels.
- Opt-Out Handling: Regulations (like TCPA in the US) require handling STOP, HELP keywords. Plivo can manage standard opt-outs automatically for long codes and Toll-Free numbers if configured in the console. Ensure compliance with local regulations. For this basic guide, we rely on Plivo's default handling.
9. Implementing Performance Optimizations
For this simple single-message API, major optimizations are usually unnecessary. However, for higher volume:
- Asynchronous Operations: Node.js and the Plivo SDK are already asynchronous, preventing the server from blocking during the API call. Using
async/await
maintains this benefit with cleaner syntax. - Connection Pooling: The Plivo SDK likely handles underlying HTTP connection management. No specific action needed here.
- Payload Size: Keep request/response payloads minimal. Avoid sending excessively large JSON responses.
- Queuing for High Volume: If you need to send many messages quickly (e.g., bulk notifications), don't call the Plivo API directly within the HTTP request handler for each message. Instead:
- Accept the request quickly.
- Push the message details (to, text) onto a message queue (like RabbitMQ, Redis Streams, AWS SQS, BullMQ).
- Have separate background worker processes read from the queue and make the calls to the Plivo API. This decouples the API from the sending process, improves response times, and allows for better rate control and retries.
- Caching: Not applicable for sending unique messages. Could be relevant if fetching contact info or templates frequently.
- Load Testing: Use tools like
k6
,Artillery
, orApacheBench
(ab) to simulate concurrent users and identify bottlenecks under load. Monitor CPU, memory, and response times during tests. - Profiling: Use Node.js built-in profiler (
node --prof
) or tools like Clinic.js to identify performance hotspots in your code.
10. Adding Monitoring, Observability, and Analytics
For production readiness:
- Health Checks: Add a simple health check endpoint that verifies basic application status.
// Add to server.js app.get('/health', (req, res) => { // Basic check: just confirm the server is running // More advanced: check DB connection, Plivo connectivity (e.g., make a cheap API call like get_account) res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); });
- Performance Metrics: Monitor key metrics like request latency, request rate (RPM), error rates (4xx, 5xx), and resource usage (CPU, memory). Tools like Prometheus/Grafana, Datadog APM, or New Relic can track these.
- Error Tracking: Integrate an error tracking service like Sentry or Bugsnag. These automatically capture unhandled exceptions and logged errors, providing stack traces and context.
# Example: npm install @sentry/node @sentry/tracing # Then configure in server.js (early) # const Sentry = require('@sentry/node'); # Sentry.init({ dsn: 'YOUR_SENTRY_DSN' /* ... options ... */ }); # app.use(Sentry.Handlers.requestHandler()); // The request handler must be the first middleware # // All controllers/routes should come here # app.use(Sentry.Handlers.errorHandler()); // The error handler must be before any other error middleware and after all controllers