This guide provides a comprehensive walkthrough for building a Node.js application using the Express framework to send SMS messages via the Plivo API. We'll cover everything from initial project setup to deploying a functional API endpoint.
By the end of this tutorial, you will have a simple but robust Express application capable of accepting API requests to send SMS messages, complete with basic error handling and considerations for production environments.
Project Overview and Goals
Goal: To create a backend service using Node.js and Express that exposes an API endpoint for sending SMS messages through Plivo.
Problem Solved: This service enables applications to programmatically send SMS notifications, alerts, or messages without needing to handle the complexities of SMS gateway protocols directly.
Technologies Used:
- Node.js: A JavaScript runtime environment ideal for building scalable, non-blocking, event-driven servers.
- Express.js: A minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
- Plivo Node.js SDK: A library provided by Plivo to simplify interactions with their SMS API.
- Plivo: A cloud communications platform providing APIs for SMS, voice, and more.
- dotenv: A zero-dependency module that loads environment variables from a
.env
file intoprocess.env
.
System Architecture:
+-------------+ +------------------------+ +--------------+ +------------------+
| User / App | ----> | Node.js/Express Server | ----> | Plivo SDK | ----> | Plivo API Server |
| (API Client)| | (Your Application) | | (HTTP Calls) | | (Sends SMS) |
+-------------+ +------------------------+ +--------------+ +------------------+
| | |
| POST /send-sms | Uses Plivo Credentials | Delivers SMS
| { ""to"": ..., ""text"": ... } | | to Carrier
| | Handles request, |
| | calls Plivo SDK |
+------------------------+--------------------------------------------------------+
Prerequisites:
- Node.js and npm (or yarn): Installed on your development machine. Download from nodejs.org.
- Plivo Account: Sign up for a free trial at plivo.com.
- Plivo Auth ID and Auth Token: Found on your Plivo Console dashboard after logging in.
- Plivo Phone Number or Sender ID:
- US/Canada: You must use a Plivo phone number capable of sending SMS. Purchase one via the Plivo Console (Phone Numbers -> Buy Numbers).
- Other Countries: You might be able to use a registered Alphanumeric Sender ID (check Plivo documentation and local regulations) or a Plivo phone number.
- Verified Destination Number (Trial Accounts): If using a Plivo trial account, you can only send SMS to numbers verified in your Plivo Console (Phone Numbers -> 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-sms-sender cd plivo-sms-sender
-
Initialize Node.js Project: Initialize the project using npm. The
-y
flag accepts the default settings.npm init -y
This creates a
package.json
file. -
Install Dependencies: Install Express for the web server, the Plivo SDK for interacting with the API, and
dotenv
for managing environment variables securely.npm install express plivo dotenv
-
Create Project Structure: Create the main application file and a file for environment variables.
touch app.js .env .gitignore
Your basic structure should look like:
plivo-sms-sender/ ├── app.js ├── node_modules/ ├── package.json ├── package-lock.json ├── .env └── .gitignore
-
Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing dependencies and sensitive credentials.node_modules .env
Why
.gitignore
? It prevents accidentally publishing sensitive information (like API keys in.env
) and unnecessary large directories (node_modules
) to version control systems like Git.
2. Environment Configuration
Store your sensitive Plivo credentials securely using environment variables.
-
Edit the
.env
file: Open the.env
file you created and add your Plivo credentials and sender information.# Plivo API Credentials - Find these on your Plivo Console Dashboard PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN # Plivo Sender Information # Use a Plivo phone number (E.164 format, e.g., +14155551234) for US/Canada # Or use a registered Alphanumeric Sender ID for supported countries PLIVO_SENDER_ID=+1XXXXXXXXXX # Server Configuration PORT=3000
-
Replace Placeholders:
- Replace
YOUR_PLIVO_AUTH_ID
andYOUR_PLIVO_AUTH_TOKEN
with the actual values from your Plivo Console. - Replace
+1XXXXXXXXXX
with your SMS-enabled Plivo phone number (in E.164 format) or your registered Alphanumeric Sender ID.
- Replace
Environment Variable Explanation:
PLIVO_AUTH_ID
: Your unique Plivo account identifier. How to obtain: Log in to the Plivo Console; it's displayed prominently on the dashboard.PLIVO_AUTH_TOKEN
: Your secret token for API authentication. How to obtain: Log in to the Plivo Console; it's displayed below the Auth ID. Treat this like a password.PLIVO_SENDER_ID
: The identifier ('From' number or Alphanumeric ID) that will appear on the recipient's device. How to obtain/configure: For US/Canada, purchase an SMS-enabled number from Phone Numbers -> Buy Numbers in the Plivo Console. For other regions, check Sender ID registration requirements with Plivo support or documentation.PORT
: The network port your Express application will listen on.3000
is a common default for development.
Why use .env
? It keeps sensitive credentials out of your source code, making your application more secure and easier to configure for different environments (development, staging, production).
3. Implementing Core Functionality (Basic Send)
Let's start by initializing the Plivo client and creating a function to send a single SMS.
-
Edit
app.js
: Openapp.js
and add the following code to set up Express, load environment variables, initialize the Plivo client, and define our API endpoints.// app.js 'use strict'; // Enforces stricter parsing and error handling const express = require('express'); const plivo = require('plivo'); require('dotenv').config(); // Load environment variables from .env file // Validate essential environment variables if (!process.env.PLIVO_AUTH_ID || !process.env.PLIVO_AUTH_TOKEN || !process.env.PLIVO_SENDER_ID) { console.error(""Error: Plivo credentials or sender ID missing in .env file.""); console.error(""Please ensure PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, and PLIVO_SENDER_ID are set.""); process.exit(1); // Exit if critical configuration is missing } const app = express(); app.use(express.json()); // Middleware to parse JSON request bodies const port = process.env.PORT || 3000; // Initialize Plivo client let client; try { client = new plivo.Client(process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN); console.log(""Plivo client initialized successfully.""); } catch (error) { console.error(""Failed to initialize Plivo client:"", error); process.exit(1); // Exit if client cannot be initialized } // --- API Endpoints --- /** * @route POST /send-sms * @description Sends a single SMS message. * @body {string} to - Destination phone number in E.164 format (e.g., +14155551234). * @body {string} text - The message content. Max 160 GSM characters or 70 Unicode characters per segment. */ app.post('/send-sms', async (req, res) => { const { to, text } = req.body; const src = process.env.PLIVO_SENDER_ID; // --- Input Validation --- if (!to || !text) { console.error(""Validation Error: 'to' and 'text' fields are required.""); return res.status(400).json({ error: ""'to' and 'text' fields are required."" }); } // Basic E.164 format check (starts with '+', followed by digits) // For production, consider more robust validation using libraries like 'joi' or 'express-validator'. if (!/^\+\d+$/.test(to)) { console.error(`Validation Error: Invalid 'to' number format: ${to}. Must be E.164 format.`); return res.status(400).json({ error: ""Invalid 'to' number format. Must start with '+' followed by digits (E.164 format)."" }); } // Basic text length check (optional, Plivo handles segmentation) if (text.length === 0) { console.error(`Validation Error: 'text' field cannot be empty.`); return res.status(400).json({ error: ""'text' field cannot be empty."" }); } console.log(`Attempting to send SMS from ${src} to ${to}`); try { const response = await client.messages.create( src, // Sender ID from .env to, // Destination number from request body text // Message text from request body ); console.log(""Plivo API Response:"", response); // Plivo's API usually returns a 202 Accepted on success // The actual delivery status comes via webhooks (if configured) res.status(202).json({ message: ""SMS request accepted by Plivo."", message_uuid: response.messageUuid // Plivo's unique ID for the message request }); } catch (error) { // Log detailed error information internally console.error(`[Plivo API Error] Failed sending SMS to ${to}. Status: ${error.statusCode || 'N/A'}. Message: ${error.message}. Details:`, error); // Send a standardized error response to the client res.status(error.statusCode || 500).json({ error: ""SMS sending failed."", // Optionally include a reference ID for correlation in production // error_reference: Date.now() // Simple example reference }); } }); /** * @route GET /health * @description Health check endpoint. */ app.get('/health', (req, res) => { // Basic health check: checks if the server is running // More advanced checks could verify DB connection, Plivo connectivity (with caution) etc. res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // --- Start the server --- // Check if the module is run directly if (require.main === module) { app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); } module.exports = app; // Export for testing
Code Explanation:
'use strict';
: Enables strict mode.require('dotenv').config();
: Loads variables from.env
. Must run early.- Environment Variable Check: Ensures critical Plivo config is present.
express()
: Creates an Express app.app.use(express.json())
: Middleware for parsing JSON request bodies (req.body
).plivo.Client(...)
: Initializes the Plivo SDK client. Wrapped intry...catch
for robustness./send-sms
Endpoint: Handles POST requests to send messages. Includes input validation and Plivo API call within atry...catch
block./health
Endpoint: Added a simple GET endpoint for basic health monitoring.app.listen(...)
: Starts the server. Conditional checkrequire.main === module
prevents the server from auto-starting when the file is imported for testing.module.exports = app;
: Exports the app instance for use in test files.
-
Run the Server:
node app.js
You should see
Plivo client initialized successfully.
andServer running on http://localhost:3000
. PressCtrl+C
to stop.
4. Building the API Layer
This section is now integrated into Section 3 where the app.js
code, including the /send-sms
and /health
endpoints, is presented.
Testing the Endpoints:
-
Start the server:
node app.js
-
Test
/send-sms
:- Open a new terminal window. Use
curl
or a tool like Postman. - Remember to replace
+1RECIPIENTNUMBER
with a real phone number (verified in your Plivo sandbox if using a trial account).
curl -X POST http://localhost:3000/send-sms \ -H ""Content-Type: application/json"" \ -d '{ ""to"": ""+1RECIPIENTNUMBER"", ""text"": ""Hello from Node.js and Plivo!"" }'
Expected Success Output (HTTP 202):
{ ""message"": ""SMS request accepted by Plivo."", ""message_uuid"": [""xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx""] }
Expected Validation Error (HTTP 400): (e.g., missing 'to')
{ ""error"": ""'to' and 'text' fields are required."" }
Expected Plivo Error (HTTP 500 or Plivo status): (e.g., bad credentials)
{ ""error"": ""SMS sending failed."" }
- Open a new terminal window. Use
-
Test
/health
:curl http://localhost:3000/health
Expected Output (HTTP 200):
{ ""status"": ""UP"", ""timestamp"": ""YYYY-MM-DDTHH:mm:ss.sssZ"" }
5. Integrating with Plivo (Details)
We've already initialized the client and used messages.create
in app.js
. Key points:
- Authentication: Handled by
new plivo.Client(process.env.PLIVO_AUTH_ID, process.env.PLIVO_AUTH_TOKEN)
. Ensure.env
values are correct. Find credentials on the Plivo Console Dashboard. - Sender ID (
src
): Set viaPLIVO_SENDER_ID
in.env
. Must be a valid Plivo number (US/Canada) or potentially a registered Alphanumeric Sender ID. Manage numbers in the Phone Numbers section of the Plivo Console. - API Keys/Secrets: Handled via
PLIVO_AUTH_ID
andPLIVO_AUTH_TOKEN
. Use.env
locally and secure environment variables in production. Never commit credentials. - Fallback Mechanisms: The current example lacks automatic fallback. For production:
- Consider retrying failed requests (see Section 6 concept).
- Use multiple Plivo numbers or alternative providers for high availability (complex).
- Monitor Plivo's status: status.plivo.com.
6. Implementing Error Handling and Logging
Our app.js
includes basic try...catch
blocks and improved console.error
logging.
Retry Mechanisms (Conceptual Enhancement):
Plivo requests might fail transiently. Implementing retries can improve reliability. Note: The following is a conceptual example to illustrate the pattern. It is not integrated into the main app.js
code provided in Section 3. Implementing this would require modifying the /send-sms
handler to use this function instead of calling client.messages.create
directly.
Conceptual Example Function:
async function sendSmsWithRetry(src, to, text, retries = 3, delay = 1000) {
try {
// Assume 'client' is the initialized Plivo client available in this scope
return await client.messages.create(src, to, text);
} catch (error) {
// Only retry on specific error types (e.g., 5xx server errors, maybe 429 rate limit)
// Avoid retrying permanent errors like 400 Bad Request or 401 Unauthorized.
const shouldRetry = retries > 0 && (error.statusCode >= 500 || error.statusCode === 429);
if (shouldRetry) {
console.warn(`Retrying SMS to ${to}. Retries left: ${retries - 1}. Delay: ${delay}ms. Error: ${error.message}`);
await new Promise(resolve => setTimeout(resolve, delay));
// Exponential backoff: double the delay for next retry
return sendSmsWithRetry(src, to, text, retries - 1, delay * 2);
} else {
// Don't retry permanent errors or if retries exhausted
console.error(`SMS to ${to} failed permanently or retries exhausted. Error: ${error.message}`);
throw error; // Re-throw the error to be caught by the main handler
}
}
}
// To use this in the app.post('/send-sms', ...) handler, you would replace:
// const response = await client.messages.create(src, to, text);
// With:
// const response = await sendSmsWithRetry(src, to, text);
Why Retry? Handles temporary network or service issues. Exponential backoff prevents overwhelming the API during outages. Caution: Carefully choose which errors to retry.
Testing Error Scenarios:
- Provide invalid credentials in
.env
. - Send requests with invalid
to
number format (e.g., ""12345""). - Use an invalid/non-existent Plivo
src
number in.env
. - Send to an unverified number using a trial account.
- Temporarily disconnect internet to simulate network errors.
- Send requests rapidly to test potential rate limits.
7. Adding Security Features
Protecting your API is crucial.
-
Input Validation and Sanitization:
- Our
app.js
includes basic checks forto
(presence, format) andtext
(presence). - Enhancement: For production, use dedicated validation libraries like
joi
orexpress-validator
to define schemas forreq.body
, check data types, enforce length limits, and potentially sanitize inputs more rigorously. Example mention added inapp.js
comments. - Check
text
length if specific limits are desired beyond Plivo's segmentation.
- Our
-
Authentication/Authorization for Your API (Conceptual Enhancement):
-
The
/send-sms
endpoint in our baseapp.js
is currently open. This is insecure for production. -
Recommendation: Implement API Key Authentication (or other methods like JWT/OAuth).
-
Conceptual Example: The following shows how you could add API key middleware. This is not integrated into the main
app.js
example but demonstrates the principle.- Add a secret key to your
.env
:INTERNAL_API_KEY=your_secret_key_here
- Conceptual Middleware Function:
// ---- Conceptual API Key Middleware (Not applied in base app.js) ---- const API_KEY = process.env.INTERNAL_API_KEY; function authenticateApiKey(req, res, next) { if (!API_KEY) { console.error(""INTERNAL_API_KEY not set. Cannot enforce API key authentication.""); // Fail closed: If key isn't configured, deny access. return res.status(500).json({ error: 'Server configuration error' }); } const providedKey = req.headers['x-api-key']; if (!providedKey || providedKey !== API_KEY) { console.warn(`Authentication failed. Invalid or missing API key.`); // Use 403 Forbidden if the key is missing/wrong, 401 often implies possibility of authenticating return res.status(403).json({ error: 'Forbidden: Invalid API Key' }); } next(); // Key is valid, proceed } // ---- How to Apply (Example - Do not add this directly to base app.js unless implementing) ---- // To protect the /send-sms route, you would add this line *before* the route definition: // app.use('/send-sms', authenticateApiKey); // Or apply it globally (carefully): // app.use(authenticateApiKey); // Applies to all routes defined after this line // ---- End Conceptual Example ----
- Clients would need to include the header
X-API-Key: your_secret_key_here
in their requests.
- Add a secret key to your
-
-
Rate Limiting (Conceptual Enhancement):
- Prevent abuse and control costs.
- Recommendation: Use libraries like
express-rate-limit
. - Conceptual Example: This shows how to set up rate limiting. It is not integrated into the main
app.js
example.
npm install express-rate-limit
// ---- Conceptual Rate Limiting Setup (Not applied in base app.js) ---- // Place near the top of app.js const rateLimit = require('express-rate-limit'); const smsLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per `windowMs` message: { error: 'Too many SMS requests created 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 }); // ---- How to Apply (Example - Do not add this directly to base app.js unless implementing) ---- // Apply the limiter to the SMS route: // app.use('/send-sms', smsLimiter); // ---- End Conceptual Example ----
Why Rate Limit? Protects against DoS attacks and cost overruns.
-
HTTPS: Always use HTTPS in production (typically handled by load balancers, reverse proxies like Nginx, or hosting platforms) to encrypt data in transit.
8. Handling Special Cases (Briefly)
- Character Encoding (GSM vs. Unicode): Plivo auto-detects. GSM-7 (standard text) = up to 160 chars/segment. Unicode (emojis, special chars) = up to 70 chars/segment. Unicode segments cost the same but fit less text. See Plivo's Encoding Guide.
- Long Messages: Plivo handles multipart messages automatically. Each part is billed separately.
- Internationalization: Use E.164 format (
+countrycode...
). Sender ID rules vary greatly by country (check Plivo docs/regulations). - Time Zones: API uses server time (often UTC). Plivo logs are typically UTC. Delivery depends on carriers.
9. Performance Optimizations (Considerations)
-
Asynchronous Operations: Node.js +
async/await
is efficient. -
Connection Pooling (SDK): Plivo SDK likely handles this.
-
Caching: Not typically applicable for sending unique SMS.
-
Load Testing: Use tools (
k6
,artillery
,ab
) to test/send-sms
under load. Monitor server resources (CPU, memory) and response times. Exampleab
command:# Create payload.json: {""to"": ""+1..."", ""text"": ""Load test""} # Send 100 requests, 10 concurrently ab -n 100 -c 10 -p payload.json -T application/json http://localhost:3000/send-sms
10. Monitoring and Observability (Considerations)
- Health Checks: The
/health
endpoint (added in Section 3) provides a basic check. Monitoring services can use it. - Logging: Use structured logging (e.g., Winston, Pino) and centralize logs (Datadog, Splunk, ELK) in production. Include correlation IDs.
- Error Tracking: Services like Sentry or Datadog APM capture and alert on errors.
- Metrics: Monitor request rate, error rate, latency (API endpoint, Plivo API), Node.js process metrics.
- Plivo Dashboard: Check Plivo Console Messages Logs for delivery status and costs.
- Plivo Webhooks (Status Callbacks): Configure a Message Request URL in Plivo for real-time status updates (requires building an endpoint to receive POSTs from Plivo).
11. Troubleshooting and Caveats
- Common Errors & Solutions:
401 Unauthorized
from Plivo: IncorrectPLIVO_AUTH_ID
orPLIVO_AUTH_TOKEN
in.env
.400 Bad Request
(Invaliddst
): Ensureto
is valid E.164 format (+1...
).400 Bad Request
(Invalidsrc
): EnsurePLIVO_SENDER_ID
is a valid, SMS-enabled Plivo number or registered ID.402 Payment Required
: Insufficient Plivo credits.- Message
Failed
/Undelivered
: Invalid destination, number blocked, carrier issues. Check Plivo logs. Trial accounts limited to verified numbers. - SMS Not Received (Plivo shows 'Delivered'): Device signal, number correct?, blocked sender?, carrier filtering?
ECONNREFUSED
/Network Errors: Server can't reach Plivo API. Check connectivity, firewalls, Plivo status (status.plivo.com).
- Platform-Specific Limitations:
- Trial Accounts: Send only to verified sandbox numbers. Messages prefixed. Limited credits.
- Sender ID Restrictions: Country-dependent (US/Canada require Plivo numbers). Check Plivo guidelines.
- Version Compatibility: Use Node.js LTS. Keep
plivo
SDK updated (npm update plivo
). - Rate Limits: Be aware of Plivo API limits. Implement app-level rate limiting (Section 7 concept) and potential retries (Section 6 concept).
- Encoding & Character Limits: GSM (160) vs. Unicode (70) impacts segment count/cost.
12. Deployment and CI/CD
- Deployment Environments: Heroku, AWS (EC2, Fargate, Lambda), DigitalOcean, etc.
- Key Principles:
- Environment Variables: Use platform's mechanism for
PLIVO_AUTH_ID
,PLIVO_AUTH_TOKEN
,PLIVO_SENDER_ID
,PORT
,INTERNAL_API_KEY
(if used), etc. NEVER hardcode. NODE_ENV=production
: Set this env var for performance.- Process Manager: Use
pm2
, systemd, Docker, or platform's manager to keep the app running. (pm2 start app.js --name plivo-sms
) - Build Step: Include if using TypeScript, etc.
- Environment Variables: Use platform's mechanism for
- CI/CD Pipeline (Conceptual):
- Trigger (e.g., push to
main
). - Checkout code.
- Install dependencies (
npm ci
). - Lint/Test (
npm test
). Fail pipeline on errors. - Build (if needed).
- Deploy to platform.
- Restart application.
- Trigger (e.g., push to
- Rollback: Have a plan to revert to a previous working version.
13. Verification and Testing
-
Manual Verification Checklist:
- Deploy to staging.
- Set environment variables correctly.
- Send test request via
curl
/Postman to/send-sms
. - Verify
202 Accepted
response andmessage_uuid
. - Check Plivo Console Logs for message status (Queued -> Sent -> Delivered).
- Confirm SMS received on test phone.
- Test
/health
endpoint -> verify200 OK
and status UP. - Test invalid input (
to
/text
missing/invalid format) -> verify400 Bad Request
. - (If implemented) Test invalid/missing API Key -> verify
403 Forbidden
. - (If implemented) Test rate limiting -> verify
429 Too Many Requests
. - Check application logs for errors.
-
Automated Testing (Examples - using Jest):
- Install Jest:
npm install --save-dev jest supertest
- Create a test file (e.g.,
app.test.js
):
// app.test.js const request = require('supertest'); const app = require('./app'); // Import your express app // Mock the Plivo client to avoid actual API calls/costs during tests // Mock implementation simulates a successful API call by default const mockCreate = jest.fn().mockResolvedValue({ message: [""SMS request queued for delivery""], messageUuid: [""mock-uuid-"" + Date.now()], apiId: ""mock-api-id"" }); jest.mock('plivo', () => ({ Client: jest.fn().mockImplementation(() => ({ messages: { create: mockCreate // Use the mock function } })) })); // Load .env for consistency, but tests might override or rely on mocks require('dotenv').config(); describe('SMS API Endpoints', () => { // Clear mocks before each test to ensure isolation beforeEach(() => { mockCreate.mockClear(); // Reset to default success behavior if needed after failure tests mockCreate.mockResolvedValue({ message: [""SMS request queued for delivery""], messageUuid: [""mock-uuid-"" + Date.now()], apiId: ""mock-api-id"" }); }); // --- Test Suite for POST /send-sms --- describe('POST /send-sms', () => { const validPayload = { to: '+15551234567', text: 'Test message' }; it('should return 400 Bad Request if ""to"" is missing', async () => { const res = await request(app) .post('/send-sms') .send({ text: 'Missing to field' }); // Missing 'to' expect(res.statusCode).toEqual(400); expect(res.body).toHaveProperty('error', ""'to' and 'text' fields are required.""); expect(mockCreate).not.toHaveBeenCalled(); // Plivo should not be called }); it('should return 400 Bad Request if ""text"" is missing', async () => { const res = await request(app) .post('/send-sms') .send({ to: '+15551234567' }); // Missing 'text' expect(res.statusCode).toEqual(400); expect(res.body).toHaveProperty('error', ""'to' and 'text' fields are required.""); expect(mockCreate).not.toHaveBeenCalled(); }); it('should return 400 Bad Request for invalid ""to"" format', async () => { const res = await request(app) .post('/send-sms') .send({ to: '1234567890', text: 'Invalid number format' }); // Invalid 'to' expect(res.statusCode).toEqual(400); expect(res.body).toHaveProperty('error', expect.stringContaining(""Invalid 'to' number format"")); expect(mockCreate).not.toHaveBeenCalled(); }); it('should return 202 Accepted and message_uuid on valid request', async () => { const res = await request(app) .post('/send-sms') .send(validPayload); // Valid payload expect(res.statusCode).toEqual(202); expect(res.body).toHaveProperty('message', 'SMS request accepted by Plivo.'); expect(res.body).toHaveProperty('message_uuid'); expect(mockCreate).toHaveBeenCalledTimes(1); expect(mockCreate).toHaveBeenCalledWith( process.env.PLIVO_SENDER_ID, // Check if called with correct args validPayload.to, validPayload.text ); }); it('should return 500 if Plivo API call fails', async () => { // Simulate Plivo SDK throwing an error const plivoError = new Error(""Plivo simulated error""); plivoError.statusCode = 503; // Example Plivo error status mockCreate.mockRejectedValueOnce(plivoError); // Make the mock throw an error const res = await request(app) .post('/send-sms') .send(validPayload); expect(res.statusCode).toEqual(503); // Should reflect Plivo's error code if available expect(res.body).toHaveProperty('error', 'SMS sending failed.'); expect(mockCreate).toHaveBeenCalledTimes(1); // Ensure it was called }); }); // --- Test Suite for GET /health --- describe('GET /health', () => { it('should return 200 OK and status UP', async () => { const res = await request(app).get('/health'); expect(res.statusCode).toEqual(200); expect(res.body).toHaveProperty('status', 'UP'); expect(res.body).toHaveProperty('timestamp'); }); }); });
- Install Jest: