Node.js (ESM)15 min setupBeginner

Modern Phone Validation Middleware for Express.js

Create clean, reusable middleware to validate phone numbers across your Express.js endpoints using modern JavaScript (ES Modules).

The Problem

Your API accepts phone numbers from multiple endpoints, but inconsistent validation leads to bad data and downstream errors.

The Solution

Create a single, reusable Express middleware that validates phone numbers using the Phone Validator API, ensuring data quality across all routes.

In any application that collects user data, ensuring the validity of phone numbers is crucial. It prevents fake sign-ups, reduces notification failures, and improves the overall quality of your user data.

This guide will walk you through creating a robust and reusable middleware for Express.js. We'll use modern JavaScript (ES Modules) and best practices to build a piece of software you can confidently drop into any project.

1Project Setup

First, let's set up our Node.js project.

# 1. Create a new directory and navigate into it
mkdir express-phone-validator && cd express-phone-validator

# 2. Initialize a new Node.js project
npm init -y

# 3. Add "type": "module" to your package.json to enable ES Modules
# This can be done manually or with a command like this:
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json')); pkg.type = 'module'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));"

# 4. Install necessary dependencies
npm install express axios dotenv

Why `type: "module"`?

Setting the `type` to `module` in `package.json` tells Node.js to treat `.js` files as ES Modules. This allows us to use modern `import` and `export` syntax, leading to cleaner, more standardized code compared to the older CommonJS (`require`/`module.exports`).

2Create an API Client

api/phoneValidatorClient.js

Before we write the middleware, it's a good practice to abstract the API interaction into its own file. This makes our code cleaner, easier to test, and reusable.

import axios from 'axios';

const API_BASE_URL = 'https://phone-validator7.p.rapidapi.com/validate';

/**
 * Validates a phone number using the Payloadic Phone Validator API.
 * @param {string} phoneNumber - The phone number to validate.
 * @returns {Promise<object>} - The validation result from the API.
 * @throws {Error} - Throws an error if the API call fails or times out.
 */
export async function validatePhoneNumber(phoneNumber) {
  const options = {
    method: 'GET',
    url: API_BASE_URL,
    params: { phone: phoneNumber },
    headers: {
      'X-RapidAPI-Key': process.env.RAPIDAPI_KEY,
      'X-RapidAPI-Host': 'phone-validator7.p.rapidapi.com'
    },
    timeout: 5000 // 5-second timeout
  };

  try {
    const response = await axios.request(options);
    return response.data;
  } catch (error) {
    console.error('Phone Validator API request failed:', error.message);
    // Re-throw the error to be handled by the middleware
    throw error;
  }
}

3Build the Validation Middleware

middleware/validatePhone.js

Now, let's create the middleware. This function will intercept requests, validate the phone number using our client, and either block the request or pass it to the next handler.

import { validatePhoneNumber } from '../api/phoneValidatorClient.js';

/**
 * Express middleware to validate a phone number from the request body.
 * @param {string} [fieldName='phone'] - The name of the field in req.body containing the phone number.
 */
export const phoneValidatorMiddleware = (fieldName = 'phone') => {
  return async (req, res, next) => {
    const phoneNumber = req.body[fieldName];

    if (!phoneNumber) {
      return res.status(400).json({
        success: false,
        error: 'Phone number field ' + fieldName + ' is required.'
      });
    }

    try {
      const validationResult = await validatePhoneNumber(phoneNumber);

      if (!validationResult.valid) {
        return res.status(400).json({
          success: false,
          error: 'Invalid phone number provided.'
        });
      }

      // Attach the validated data to the request object for later use
      req.phoneValidation = validationResult;
      
      // Proceed to the next middleware or route handler
      next();
    } catch (error) {
      // This block catches errors from our API client
      if (axios.isAxiosError(error) && (error.code === 'ECONNABORTED' || error.response?.status >= 500)) {
        // Fail-open strategy: If our validation service is down, allow the request.
        // Log this event for monitoring.
        console.warn('Phone validation service is unavailable. Allowing request for ' + phoneNumber + ' to proceed.');
        next(); 
      } else {
        // For other errors (e.g., client-side issues, invalid API key), block the request.
        res.status(500).json({
          success: false,
          error: 'An internal error occurred during phone validation.'
        });
      }
    }
  };
};

Design Choice: "Fail-Open" vs. "Fail-Closed"

In the `catch` block, we use a "fail-open" strategy. If the Phone Validator API is down or times out, we log a warning but still allow the user's request to go through. This prioritizes your application's uptime over strict validation in failure scenarios.

For critical applications (e.g., financial transactions), you might choose a "fail-closed" strategy by changing `next()` to `res.status(503).json(...)` to block the request instead.

4Integrate into an Express Server

server.js

Finally, let's create our main server file, import the middleware, and apply it to a route. We'll also create a mock User store to make this example fully runnable.

import express from 'express';
import 'dotenv/config'; // Loads .env file
import { phoneValidatorMiddleware } from './middleware/validatePhone.js';

const app = express();
const PORT = process.env.PORT || 3000;

// --- Mock User Store (for demonstration) ---
const users = [];
let currentId = 1;
const mockUserStore = {
  create: async (data) => {
    const newUser = { id: currentId++, ...data, createdAt: new Date() };
    users.push(newUser);
    return newUser;
  },
  findById: async (id) => users.find(u => u.id === id),
};
// -----------------------------------------

// Middleware to parse JSON bodies
app.use(express.json());

// Public route - no validation
app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the API!' });
});

// Protected route with phone validation
app.post(
  '/register',
  phoneValidatorMiddleware('contactPhone'), // We'll look for the 'contactPhone' field
  async (req, res) => {
    try {
      const { email, password, contactPhone } = req.body;
      
      // The phone number has already been validated by the middleware.
      // We can now safely use the validated and formatted data.
      const validatedPhoneData = req.phoneValidation;

      console.log('Registering user with validated phone data:', validatedPhoneData);

      const user = await mockUserStore.create({
        email,
        password: 'hashed_password', // In a real app, hash this!
        phone: validatedPhoneData.number, // Use the standardized number
        phoneCountry: validatedPhoneData.country_code,
        phoneCarrier: validatedPhoneData.carrier,
      });

      res.status(201).json({
        success: true,
        message: 'User registered successfully!',
        user: { id: user.id, email: user.email, phone: user.phone },
      });
    } catch (error) {
      res.status(500).json({ success: false, error: 'User registration failed.' });
    }
  }
);

app.listen(PORT, () => {
  console.log('Server is running on http://localhost:' + PORT);
});

5Running and Testing

1. Create a `.env` file in your project root and add your API key:

RAPIDAPI_KEY=your_rapidapi_key_here

2. Start your server:

node server.js

3. Test your endpoint using cURL:

curl -X POST http://localhost:3000/register   -H "Content-Type: application/json"   -d '{
    "email": "test@example.com",
    "password": "password123",
    "contactPhone": "+14155552671"
  }'

You should receive a `201 Created` response with the new user object.

Conclusion

You now have a robust, reusable, and modern middleware for validating phone numbers in any Express.js project. By abstracting the logic, using modern JavaScript, and handling errors gracefully, you've created a piece of infrastructure that improves data quality and developer experience.

From here, you can explore the advanced configurations in the API documentation to further tailor the validation to your needs, such as restricting by country, blocking certain line types (like VOIP), or adding caching.