Multimedia Messaging Service (MMS) allows you to enrich your user communication by sending messages that include images, GIFs, videos, and audio, alongside text. While standard Short Message Service (SMS) is limited to text, MMS provides a more engaging and informative experience.
This guide provides a comprehensive, step-by-step walkthrough for building a production-ready Node.js application capable of sending MMS messages using the Plivo communications platform API. We will build a simple API endpoint that accepts recipient details and media information, then reliably sends the MMS via Plivo.
Project Goals:
- Set up a Node.js project using Express.js.
- Integrate the Plivo Node.js SDK for sending MMS.
- Build a secure API endpoint to trigger MMS sending.
- Implement proper configuration management for API keys and settings.
- Include input validation, error handling, and basic logging.
- Provide instructions for testing and verification.
- Discuss deployment considerations and potential caveats.
Technologies Used:
- Node.js: A JavaScript runtime environment for server-side development.
- Express.js: A minimal and flexible Node.js web application framework.
- Plivo Node.js SDK: Simplifies interaction with the Plivo API.
- dotenv: Manages environment variables for configuration.
- express-validator: Middleware for request data validation.
- Postman / curl: For testing the API endpoint.
Prerequisites:
- Node.js and npm (or yarn) installed.
- A Plivo account (a free trial account is sufficient to start).
- Basic understanding of JavaScript, Node.js, APIs, and terminal commands.
- A publicly accessible URL for your media file (e.g., hosted on AWS S3, Cloudinary, or even a public Giphy link for testing).
System Architecture:
graph LR
A[Client / User] -- HTTP POST Request --> B(Node.js / Express API);
B -- Send MMS Request --> C(Plivo API);
C -- Delivers MMS --> D(Recipient's Mobile Device);
B -- API Response (Success/Error) --> A;
subgraph 'Our Application'
B
end
subgraph 'External Service'
C
end
This guide will walk you through creating the ""Node.js / Express API"" component.
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 node-plivo-mms cd node-plivo-mms
-
Initialize Node.js Project: This creates a
package.json
file to manage dependencies and project information.npm init -y
-
Install Dependencies: We need
express
for the web server,plivo
for the SDK,dotenv
for environment variables, andexpress-validator
for input validation.npm install express plivo dotenv express-validator
express
: The web framework.plivo
: The official Plivo SDK.dotenv
: Loads environment variables from a.env
file intoprocess.env
. Essential for keeping secrets out of code.express-validator
: Provides middleware for validating incoming request data, crucial for security and data integrity.
-
Set Up Project Structure: Create the following basic structure within your
node-plivo-mms
directory:node-plivo-mms/ ├── src/ │ ├── controllers/ │ │ └── mmsController.js │ ├── middleware/ │ │ └── validators.js │ ├── routes/ │ │ └── mmsRoutes.js │ ├── services/ │ │ └── plivoService.js │ └── app.js ├── .env ├── .gitignore └── package.json
src/
: Contains the main application code.controllers/
: Handles incoming requests, validates data, and calls services.middleware/
: Contains custom middleware, like our validators.routes/
: Defines API endpoints and maps them to controllers.services/
: Contains business logic, including interaction with external APIs like Plivo.app.js
: Sets up the Express application, middleware, and starts the server..env
: Stores sensitive configuration like API keys..gitignore
: Prevents sensitive files (like.env
) and unnecessary files (likenode_modules
) from being committed to version control.
-
Create
.gitignore
: Create a file named.gitignore
in the project root and add the following:# Dependencies node_modules/ # Environment variables .env # Logs logs *.log # OS generated files .DS_Store Thumbs.db
-
Set Up Environment Variables (
.env
): Create a file named.env
in the project root. This file will hold your Plivo credentials and configuration. Never commit this file to Git.# Plivo Credentials PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN # Plivo MMS Enabled Number (must be purchased from Plivo and support MMS) PLIVO_SOURCE_NUMBER=YOUR_PLIVO_MMS_NUMBER # Server Configuration PORT=3000
PLIVO_AUTH_ID
,PLIVO_AUTH_TOKEN
: Your Plivo API credentials.PLIVO_SOURCE_NUMBER
: The Plivo phone number (in E.164 format, e.g.,+14155551212
) that will send the MMS. It must be MMS-enabled.PORT
: The port on which your Node.js application will listen.
2. Plivo Account Setup
Before writing code, ensure your Plivo account is ready.
- Sign Up/Log In: Go to Plivo.com and sign up for an account or log in.
- Find Auth ID and Auth Token:
- Log in to the Plivo console.
- Your
Auth ID
andAuth Token
are prominently displayed on the overview dashboard page. - Copy these values and paste them into your
.env
file forPLIVO_AUTH_ID
andPLIVO_AUTH_TOKEN
.
- Get an MMS-Enabled Phone Number:
- Navigate to ""Phone Numbers"" > ""Buy Numbers"" in the Plivo console.
- Search for numbers, ensuring you filter by ""MMS"" capability. Plivo currently supports MMS in the US and Canada.
- Purchase a suitable number.
- Go to ""Phone Numbers"" > ""Your Numbers"". Verify that the number you purchased has the MMS capability listed.
- Copy this number (including the leading
+
and country code) and paste it into your.env
file forPLIVO_SOURCE_NUMBER
.
- Trial Account Limitation (Sandbox Numbers):
- IMPORTANT: If you are using a Plivo trial account, you can only send messages to phone numbers that have been verified in your account. This is a very common reason for failures when starting out.
- Navigate to ""Messaging"" > ""Sandbox Numbers"".
- You MUST add and verify the phone number(s) you intend to use as recipients during testing here. Failure to do so will result in failed messages.
3. Implementing Core MMS Sending Logic (Service)
We'll encapsulate the Plivo interaction within a dedicated service file.
Create src/services/plivoService.js
:
// src/services/plivoService.js
const plivo = require('plivo');
require('dotenv').config(); // Ensure environment variables are loaded
// Initialize the Plivo client only once using environment variables
// Note: Client initialization captures env vars at the time of require/load.
// For tests involving changing credentials, ensure modules are reset.
let client;
try {
client = new plivo.Client(
process.env.PLIVO_AUTH_ID,
process.env.PLIVO_AUTH_TOKEN
);
} catch (error) {
console.error(""Failed to initialize Plivo client. Check AUTH_ID/AUTH_TOKEN."", error);
// Depending on application needs, you might want to prevent startup
// or handle this more gracefully than just logging.
}
/**
* Sends an MMS message using Plivo.
* Reads sensitive config directly inside the function for better testability
* and to reflect potential runtime changes if not using singleton patterns.
*
* @param {string} destinationNumber - The recipient's phone number (E.164 format).
* @param {string} text - The text content of the message.
* @param {string} mediaUrl - The publicly accessible URL of the media file.
* @returns {Promise<object>} - A promise that resolves with the Plivo API response.
* @throws {Error} - Throws an error if sending fails or config is missing.
*/
const sendMms = async (destinationNumber, text, mediaUrl) => {
const sourceNumber = process.env.PLIVO_SOURCE_NUMBER;
const authId = process.env.PLIVO_AUTH_ID;
const authToken = process.env.PLIVO_AUTH_TOKEN;
// Check for essential configuration *inside* the function call
if (!sourceNumber) {
throw new Error('Plivo source number is not configured in .env');
}
if (!authId || !authToken) {
throw new Error('Plivo Auth ID or Auth Token is missing in .env');
}
if (!client) {
throw new Error('Plivo client is not initialized. Check credentials.');
}
// Log initiation attempt (consider replacing with a proper logger in production)
console.log(`Attempting to send MMS to ${destinationNumber} from ${sourceNumber}`);
try {
const response = await client.messages.create(
sourceNumber, // src
destinationNumber, // dst
text, // text
{
type: 'mms',
media_urls: [mediaUrl], // Array of media URLs
// You can also use media_ids if you upload media via the Plivo console:
// media_ids: ['YOUR_UPLOADED_MEDIA_ID']
},
// Optional: Callback URL for delivery status updates
// {
// url: 'YOUR_STATUS_CALLBACK_URL',
// method: 'POST'
// }
);
// Log success (consider replacing with a proper logger in production)
console.log('Plivo API Response:', response);
return response;
} catch (error) {
// Log error (consider replacing with a proper logger in production)
console.error('Error sending MMS via Plivo:', error);
// Re-throw the error to be handled by the controller/middleware
throw error;
}
};
module.exports = {
sendMms,
};
Explanation:
- We import
plivo
and load environment variables usingdotenv.config()
. - The Plivo
Client
is initialized. Note that it capturesPLIVO_AUTH_ID
andPLIVO_AUTH_TOKEN
at the time it's first loaded. - The
sendMms
function takes the destination number, text, and media URL as arguments. - Checks for essential configuration (
sourceNumber
,PLIVO_AUTH_ID
,PLIVO_AUTH_TOKEN
) are performed inside the function to make testing environment variable changes easier. client.messages.create
is the core Plivo SDK method used.src
: Your Plivo MMS number (PLIVO_SOURCE_NUMBER
).dst
: The recipient's number.text
: The message body.- The fourth argument is an options object where we specify
type: 'mms'
and provide themedia_urls
as an array. Plivo will fetch the media from this URL and attach it. - The URL in
media_urls
must be publicly accessible. - An optional fifth argument can provide callback details for status updates (not implemented in this basic guide).
- We use
async/await
for cleaner asynchronous code. - Note on Logging:
console.log
andconsole.error
are used here for simplicity. In a production environment, replace these with a robust logging library like Winston or Pino (discussed further in Deployment section). - Errors during the API call are caught, logged, and re-thrown for higher-level handling.
4. Building the API Layer (Routes and Controller)
Now, let's create the Express route and controller to expose our MMS sending functionality via an API endpoint.
1. Create Route (src/routes/mmsRoutes.js
):
// src/routes/mmsRoutes.js
const express = require('express');
const mmsController = require('../controllers/mmsController');
const { validateSendMms } = require('../middleware/validators'); // We'll create this next
const router = express.Router();
// Define the endpoint: POST /api/mms/send
// It uses validation middleware before hitting the controller
router.post('/send', validateSendMms, mmsController.sendMmsHandler);
module.exports = router;
Explanation:
- We create an Express router instance.
- We define a
POST
route at/send
. The full path will be/api/mms/send
when mounted inapp.js
. - Crucially, we include
validateSendMms
middleware (which we'll create next) to validate the request body before themmsController.sendMmsHandler
is executed. - The request is then passed to the controller function.
2. Create Controller (src/controllers/mmsController.js
):
// src/controllers/mmsController.js
const { validationResult } = require('express-validator');
const plivoService = require('../services/plivoService');
/**
* Handles the API request to send an MMS.
* Validates input, calls the Plivo service, and sends the response.
*/
const sendMmsHandler = async (req, res, next) => {
// Check for validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
// If validation fails, return 400 Bad Request with error details
return res.status(400).json({ errors: errors.array() });
}
// Extract validated data from request body
const { destinationNumber, text, mediaUrl } = req.body;
try {
// Call the service function to send the MMS
const result = await plivoService.sendMms(destinationNumber, text, mediaUrl);
// Send success response back to the client
res.status(200).json({
message: 'MMS sending initiated successfully.',
plivoResponse: result,
});
} catch (error) {
// Pass errors to the central error handling middleware (created later)
next(error);
}
};
module.exports = {
sendMmsHandler,
};
Explanation:
- We import the
validationResult
function fromexpress-validator
and ourplivoService
. - The
sendMmsHandler
function first checks ifvalidationResult(req)
found any errors defined by our validator middleware. If so, it immediately sends a400 Bad Request
response. - If validation passes, it extracts the
destinationNumber
,text
, andmediaUrl
from the request body (req.body
). - It calls
plivoService.sendMms
within atry...catch
block. - On success, it sends a
200 OK
response including the Plivo API's confirmation. - If
plivoService.sendMms
throws an error (either configuration error or Plivo API error), thecatch
block catches it and passes it to Express's error handling mechanism usingnext(error)
.
5. Implementing Input Validation
Validating input is critical for security and preventing errors. We'll use express-validator
.
Create the directory src/middleware
and inside it, create validators.js
:
// src/middleware/validators.js
const { body } = require('express-validator');
const validateSendMms = [
// Validate destinationNumber: must be a non-empty string, likely in E.164 format
// Note: Strict E.164 validation can be complex, this is a basic check.
// Consider using a library like 'google-libphonenumber' for robust validation if needed.
body('destinationNumber')
.trim()
.notEmpty().withMessage('Destination number is required.')
.isString().withMessage('Destination number must be a string.')
.matches(/^\+[1-9]\d{1,14}$/).withMessage('Destination number must be in E.164 format (e.g., +14155551212).'),
// Validate text: must be a non-empty string
body('text')
.trim()
.notEmpty().withMessage('Text message is required.')
.isString().withMessage('Text must be a string.'),
// Validate mediaUrl: must be a non-empty string and a valid URL
body('mediaUrl')
.trim()
.notEmpty().withMessage('Media URL is required.')
.isURL().withMessage('Media URL must be a valid URL.'),
];
module.exports = {
validateSendMms,
};
Explanation:
- We use the
body
function fromexpress-validator
to define validation rules for fields expected in the request body. destinationNumber
: We check it's not empty, is a string, and roughly matches the E.164 format (starts with+
, followed by digits). For production, consider more robust phone number validation.text
: Checked for non-emptiness and being a string.mediaUrl
: Checked for non-emptiness and being a valid URL format usingisURL()
..trim()
removes leading/trailing whitespace..withMessage()
provides custom error messages.- This
validateSendMms
array of middleware is exported and used insrc/routes/mmsRoutes.js
.
6. Setting Up the Express App and Error Handling
Now, let's tie everything together in src/app.js
.
// src/app.js
const express = require('express');
const dotenv = require('dotenv');
const mmsRoutes = require('./routes/mmsRoutes');
// Load environment variables from .env file
dotenv.config();
// Create Express app instance
const app = express();
// Middleware to parse JSON request bodies
app.use(express.json());
// Middleware to parse URL-encoded request bodies (optional, but common)
app.use(express.urlencoded({ extended: true }));
// --- Routes ---
// Mount the MMS routes under the /api/mms path
app.use('/api/mms', mmsRoutes);
// --- Basic Health Check Route ---
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
});
// --- Central Error Handling Middleware ---
// This middleware catches errors passed via next(error)
// IMPORTANT: It must be defined *after* all other app.use() and routes calls
app.use((err, req, res, next) => {
// Use a proper logger in production instead of console.error
console.error('Unhandled Error:', err);
// Determine status code: use error's status or default to 500
const statusCode = err.status || err.statusCode || 500;
// Send a generic error response
// Avoid sending detailed internal error messages in production
res.status(statusCode).json({
message: 'An unexpected error occurred.',
// Optionally include error details in development only
error: process.env.NODE_ENV === 'development' ? err.message : undefined,
});
});
// --- Start the Server ---
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
// Use a proper logger in production
console.log(`Server running on port ${PORT}`);
// Verify essential config on startup
if (!process.env.PLIVO_AUTH_ID || !process.env.PLIVO_AUTH_TOKEN) {
console.warn('WARNING: Plivo Auth ID or Token is missing in .env! Client may not initialize.');
}
if (!process.env.PLIVO_SOURCE_NUMBER) {
console.warn('WARNING: Plivo source number is missing in .env! MMS sending will fail.');
} else {
console.log(`Configured to send MMS from: ${process.env.PLIVO_SOURCE_NUMBER}`);
}
});
module.exports = app; // Export for potential testing
Explanation:
- Load
dotenv
first to ensure environment variables are available. - Create the
express
app. express.json()
: Essential middleware to parse incoming JSON request bodies (like the one we'll send to/api/mms/send
).- Mount our
mmsRoutes
under the/api/mms
prefix. So, the actual endpoint will bePOST /api/mms/send
. - Health Check: A simple
/health
endpoint is added as a best practice for monitoring. - Central Error Handling: This is a crucial piece. Any error passed via
next(error)
(like in our controller's catch block) will be caught here.- It logs the error (emphasizing that a real logger should be used in production).
- It sets an appropriate HTTP status code (defaulting to 500 Internal Server Error).
- It sends a standardized JSON error response to the client, avoiding leaking sensitive stack traces in production.
- The server starts listening on the configured
PORT
. - Startup logs check for the presence of essential Plivo configuration from
.env
. - Note on Logging: This file also uses
console.log
andconsole.warn
. As mentioned before, replace these with a dedicated logging library for production applications.
7. Security Considerations
While this guide covers basics, production systems require more robust security.
- API Key Security: Your
PLIVO_AUTH_ID
andPLIVO_AUTH_TOKEN
are highly sensitive.- NEVER commit them to version control. Use
.env
and.gitignore
. - In production deployments, use secure secret management solutions provided by your cloud provider (e.g., AWS Secrets Manager, Google Secret Manager, Azure Key Vault) or tools like HashiCorp Vault instead of
.env
files.
- NEVER commit them to version control. Use
- Input Validation: We've implemented basic validation with
express-validator
. Ensure your validation rules are strict and cover all expected inputs to prevent injection attacks or unexpected behavior. - Rate Limiting: Protect your API endpoint (and your Plivo account balance) from abuse by implementing rate limiting. The
express-rate-limit
package is a popular choice.Add it tonpm install express-rate-limit
app.js
before your routes:// src/app.js const rateLimit = require('express-rate-limit'); // ... other imports const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers message: 'Too many requests from this IP, please try again after 15 minutes', }); // Apply the rate limiting middleware to API calls // Apply *before* the specific routes you want to limit app.use('/api/mms', limiter); // Apply specifically to MMS routes // --- Routes --- app.use('/api/mms', mmsRoutes); // ... rest of app.js
- Authentication/Authorization: This guide doesn't include authentication. In a real application, you would protect the
/api/mms/send
endpoint so only authorized users or systems can trigger MMS sending (e.g., using API keys, JWT tokens, OAuth). - HTTPS: Always run your application behind a reverse proxy (like Nginx or Caddy) configured with TLS/SSL certificates (e.g., from Let's Encrypt) to ensure communication is encrypted. Do not run Node.js directly exposed to the internet on port 80/443.
8. Testing
Testing ensures your code works as expected and helps prevent regressions.
1. Unit Testing (Plivo Service): We can unit test the service layer by mocking the Plivo client.
Install Jest:
npm install --save-dev jest
Add a test script to package.json
:
// package.json
{
// ... other fields
""scripts"": {
""start"": ""node src/app.js"",
""test"": ""jest""
},
// ...
}
Create a test file src/services/plivoService.test.js
:
// src/services/plivoService.test.js
// Mock the plivo module *before* importing the service
const mockCreate = jest.fn();
jest.mock('plivo', () => {
// Mock the constructor and the messages.create method
return {
Client: jest.fn().mockImplementation(() => {
// This is the instance returned by new plivo.Client()
return {
messages: {
create: mockCreate,
},
};
}),
};
});
// Mock dotenv - prevent it from actually reading .env during tests
jest.mock('dotenv', () => ({
config: jest.fn(),
}));
// Store original process.env
const OLD_ENV = process.env;
describe('Plivo Service', () => {
beforeEach(() => {
// Reset modules before each test to clear cache and re-require with fresh env vars
jest.resetModules();
// Clear mock function calls history
mockCreate.mockClear();
// Restore process.env and set default test values
process.env = {
...OLD_ENV, // Preserve original env vars
PLIVO_AUTH_ID: 'test-auth-id',
PLIVO_AUTH_TOKEN: 'test-auth-token',
PLIVO_SOURCE_NUMBER: '+15551112222',
};
});
afterAll(() => {
// Restore original environment variables after all tests
process.env = OLD_ENV;
});
it('should call plivo.messages.create with correct parameters', async () => {
// Re-require the service *inside the test* after setting env vars and resetting modules
const plivoService = require('./plivoService');
const dest = '+15553334444';
const text = 'Test message';
const mediaUrl = 'http://example.com/image.jpg';
const expectedPlivoResponse = { messageUuid: 'some-uuid' };
// Configure the mock to return a resolved promise
mockCreate.mockResolvedValue(expectedPlivoResponse);
const result = await plivoService.sendMms(dest, text, mediaUrl);
// Assert that plivo.messages.create was called once
expect(mockCreate).toHaveBeenCalledTimes(1);
// Assert it was called with the correct arguments
expect(mockCreate).toHaveBeenCalledWith(
process.env.PLIVO_SOURCE_NUMBER, // src
dest, // dst
text, // text
{ type: 'mms', media_urls: [mediaUrl] } // options
);
// Assert the function returned the expected response
expect(result).toEqual(expectedPlivoResponse);
});
it('should throw an error if Plivo API fails', async () => {
// Re-require the service
const plivoService = require('./plivoService');
const dest = '+15553334444';
const text = 'Test message';
const mediaUrl = 'http://example.com/image.jpg';
const expectedError = new Error('Plivo API Error');
// Configure the mock to return a rejected promise
mockCreate.mockRejectedValue(expectedError);
// Assert that calling sendMms throws an error
await expect(plivoService.sendMms(dest, text, mediaUrl))
.rejects.toThrow('Plivo API Error');
// Ensure plivo.messages.create was still called
expect(mockCreate).toHaveBeenCalledTimes(1);
});
it('should throw an error if source number is not configured', async () => {
// Modify environment for this specific test
delete process.env.PLIVO_SOURCE_NUMBER;
// Re-require the service *after* modifying env and resetting modules (via beforeEach)
const plivoService = require('./plivoService');
await expect(plivoService.sendMms('+111', 'txt', 'url'))
.rejects.toThrow('Plivo source number is not configured in .env');
// Ensure Plivo API was not called
expect(mockCreate).not.toHaveBeenCalled();
});
it('should throw an error if auth credentials are not configured', async () => {
// Modify environment for this specific test
delete process.env.PLIVO_AUTH_ID;
// Re-require the service *after* modifying env and resetting modules (via beforeEach)
const plivoService = require('./plivoService');
await expect(plivoService.sendMms('+111', 'txt', 'url'))
.rejects.toThrow('Plivo Auth ID or Auth Token is missing in .env');
// Ensure Plivo API was not called
expect(mockCreate).not.toHaveBeenCalled();
});
});
Run tests: npm test
Explanation of Test Changes:
jest.resetModules()
is added tobeforeEach
to ensure thatplivoService
(and its dependency onprocess.env
) is freshly loaded for each test, reflecting any changes made toprocess.env
.- The service is now
require
d inside each test case (or could be done once inbeforeEach
after settingprocess.env
) to ensure the test uses the environment variables set for that specific test scope. - Added a test case for missing Auth ID/Token.
2. Integration Testing (API Endpoint): Test the full request-response cycle of your API endpoint.
Install Supertest:
npm install --save-dev supertest
Create a test file src/routes/mmsRoutes.test.js
:
// src/routes/mmsRoutes.test.js
const request = require('supertest');
const app = require('../app'); // Import your configured Express app
const plivoService = require('../services/plivoService'); // Import to mock
// Mock the service layer to avoid actual Plivo calls during integration tests
// Jest automatically hoist jest.mock calls to the top of the module
jest.mock('../services/plivoService');
describe('MMS API Endpoint (/api/mms/send)', () => {
beforeEach(() => {
// Reset mocks before each test
// Ensure the mock implementation is cleared or reset if needed
// For jest.fn(), mockClear() is usually sufficient.
plivoService.sendMms.mockClear();
});
it('should send MMS successfully with valid input', async () => {
const requestBody = {
destinationNumber: '+14155551234',
text: 'Hello via API',
mediaUrl: 'https://media.giphy.com/media/26gscSULUcfKU7dHq/source.gif',
};
const mockPlivoResponse = { message_uuid: ['mock-uuid-123'] };
// Configure the mock service function to resolve successfully
plivoService.sendMms.mockResolvedValue(mockPlivoResponse);
const response = await request(app)
.post('/api/mms/send')
.send(requestBody)
.expect('Content-Type', /json/)
.expect(200);
// Check if the service was called correctly
expect(plivoService.sendMms).toHaveBeenCalledTimes(1);
expect(plivoService.sendMms).toHaveBeenCalledWith(
requestBody.destinationNumber,
requestBody.text,
requestBody.mediaUrl
);
// Check the response body
expect(response.body.message).toEqual('MMS sending initiated successfully.');
expect(response.body.plivoResponse).toEqual(mockPlivoResponse);
});
it('should return 400 Bad Request for invalid input (missing field)', async () => {
const invalidRequestBody = {
// destinationNumber is missing
text: 'Missing number',
mediaUrl: 'https://example.com/image.png',
};
const response = await request(app)
.post('/api/mms/send')
.send(invalidRequestBody)
.expect('Content-Type', /json/)
.expect(400);
// Check the error response structure
expect(response.body.errors).toBeDefined();
expect(response.body.errors.length).toBeGreaterThan(0);
expect(response.body.errors[0].path).toEqual('destinationNumber'); // Check which field failed
expect(response.body.errors[0].msg).toContain('required'); // Check error message
// Ensure the service was NOT called
expect(plivoService.sendMms).not.toHaveBeenCalled();
});
it('should return 400 Bad Request for invalid URL format', async () => {
const invalidRequestBody = {
destinationNumber: '+14155551234',
text: 'Bad URL',
mediaUrl: 'not-a-valid-url', // Invalid URL
};
const response = await request(app)
.post('/api/mms/send')
.send(invalidRequestBody)
.expect('Content-Type', /json/)
.expect(400);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].path).toEqual('mediaUrl');
expect(response.body.errors[0].msg).toContain('valid URL');
expect(plivoService.sendMms).not.toHaveBeenCalled();
});
it('should return 500 Internal Server Error if service throws an error', async () => {
const requestBody = {
destinationNumber: '+14155551234',
text: 'This will fail',
mediaUrl: 'https://example.com/fail.gif',
};
const errorMessage = 'Plivo service failed';
// Configure the mock service to reject
plivoService.sendMms.mockRejectedValue(new Error(errorMessage));
const response = await request(app)
.post('/api/mms/send')
.send(requestBody)
.expect('Content-Type', /json/)
.expect(500);
// Check the generic error response
expect(response.body.message).toEqual('An unexpected error occurred.');
// Optionally check error message in development
// Note: Requires setting NODE_ENV=development for the test runner
// if (process.env.NODE_ENV === 'development') {
// expect(response.body.error).toEqual(errorMessage);
// }
// Ensure the service was called
expect(plivoService.sendMms).toHaveBeenCalledTimes(1);
});
});
Run tests: npm test
9. Running the Application
- Ensure
.env
is correct: Double-check yourPLIVO_AUTH_ID
,PLIVO_AUTH_TOKEN
, andPLIVO_SOURCE_NUMBER
in the.env
file. - Start the server:
You should see output indicating the server is running and the configured source number.
node src/app.js