Skip to content
Buy 10+ on select items — save 10% auto-applied
Free US shipping on orders $35+
Order by 3pm ET — ships same-day from the US
Skip to main content

MERN Twilio SMS: Add Phone Verification to Registration | ShillehTek

October 23, 2023 117 views

MERN Twilio SMS: Add Phone Verification to Registration | ShillehTek
Project

Build MERN + Twilio SMS phone verification with a React Native Expo UI and Node/Express backend to reduce spam signups and boost security, by ShillehTek.

45 min Intermediate

Video Tutorial (Optional)

Watch first if you want to follow the full build in real time.

Project Overview

MERN + Twilio Phone Verification: In this tutorial, you add Twilio SMS phone number verification to a MERN registration flow so users must confirm a code before completing signup. This helps reduce spam users and improves application security.

This is a barebones solution meant to give you a working reference you can adapt to your own app.

React Native Expo registration screen for MERN app phone number signup before Twilio SMS verification
Example registration screen.
React Native Expo verification screen where user enters Twilio SMS code to verify phone number
Example verification screen.
  • Time: 45 to 90 minutes
  • Skill level: Intermediate
  • What you will build: A simple registration and SMS verification flow using React Native (Expo) and a Node/Express backend with Twilio

Parts List

From ShillehTek

  • None required for this software tutorial.

External

  • Twilio account - used to send SMS verification codes
  • Node.js + Express - backend API for /register and /verify
  • MongoDB + Mongoose - store users and the generated verification code
  • React Native (Expo) - mobile frontend used for the registration and verification screens
  • Axios - HTTP client used by the frontend to call the backend
  • Project code on GitHub
  • YouTube channel (optional)
  • Support link (optional)

Note: Phone numbers must include the country code for Twilio to deliver SMS. Example: United States numbers start with +1.

Step-by-Step Guide

Step 1 - Create your Twilio account

Goal: Get Twilio credentials and a Twilio phone number so your backend can send verification SMS messages.

What to do: Create an account at https://www.twilio.com/. Twilio is initially free (no credit card required) and includes a small credit balance for development. During setup, Twilio provides:

  • A Twilio phone number
  • An Account SID
  • An Auth Token

Expected result: You have a Twilio phone number, Account SID, and Auth Token ready to place into your backend config.

Step 2 - Build the frontend registration screen

Goal: Collect user info and call your backend /register endpoint, then route the user to the verification screen.

What to do: In your React Native (Expo) project, create a Registration screen like the code below. It posts name, password, and phoneNumber to http://localhost:3000/register. If the response status is 201, it navigates to the Verification screen and passes phoneNumber.

Code:

import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import axios from 'axios';

const Registration = ({ navigation }) => {
  const [name, setName] = useState('');
  const [password, setPassword] = useState('');
  const [phoneNumber, setPhoneNumber] = useState('');

  const handleRegister = async () => {
    try {
      const response = await axios.post('http://localhost:3000/register', {
        name: name,
        password: password,
        phoneNumber: phoneNumber,
      }, {headers: {
        'Content-Type': 'application/json',
      }});

      if (response.status === 201) {
        navigation.navigate('Verification', { phoneNumber });
      }
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.label}>Name:</Text>
      <TextInput
        style={styles.input}
        value={name}
        onChangeText={setName}
        placeholder="Enter your name"
      />

      <Text style={styles.label}>Password:</Text>
      <TextInput
        style={styles.input}
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        placeholder="Enter your password"
      />

      <Text style={styles.label}>Phone Number:</Text>
      <TextInput
        style={styles.input}
        value={phoneNumber}
        onChangeText={setPhoneNumber}
        secureTextEntry
        placeholder="Enter your phone number"
      />

      <Button title="Register" onPress={handleRegister} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  label: {
    fontSize: 18,
    marginBottom: 5,
  },
  input: {
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    marginBottom: 10,
    paddingLeft: 10,
  },
});

export default Registration;

Expected result: Pressing Register sends the registration payload to your backend and routes to the Verification screen if the backend returns HTTP 201.

Step 3 - Build the frontend verification screen

Goal: Let the user enter the SMS code and verify it against your backend /verify endpoint.

What to do: Create a Verification screen like the code below. It reads phoneNumber from navigation params and posts the phone number plus the user-entered verificationCode to http://localhost:3000/verify.

Important: You should unmask the phone number in your application, and you must include the area code / country code (example: +1 for the US).

Code:

import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import axios from 'axios';
import { useRoute } from '@react-navigation/native';

const Verification = () => {
  const [verificationCode, setVerificationCode] = useState('');
  const [verificationStatus, setVerificationStatus] = useState(null);
  const route = useRoute();
  const { phoneNumber } = route.params;

  const handleVerify = async () => {
    try {
      const response = await axios.post('http://localhost:3000/verify', {
        phoneNumber: phoneNumber,
        verificationCode: verificationCode
      });

      if (response.status === 200) {
        setVerificationStatus('Verification successful');
      } else {
        setVerificationStatus('Verification failed. Please try again.');
      }
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <View style={styles.container}>
      {verificationStatus && <Text style={styles.successMessage}>{verificationStatus}</Text>}
      <Text style={styles.label}>Verification Code:</Text>
      <TextInput
        style={styles.input}
        value={verificationCode}
        onChangeText={setVerificationCode}
        placeholder="Enter verification code"
      />

      <Button title="Verify" onPress={handleVerify} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
  },
  label: {
    fontSize: 18,
    marginBottom: 5,
  },
  input: {
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    marginBottom: 10,
    paddingLeft: 10,
  },
  successMessage: {
    fontSize: 16,
    color: 'green',
    marginBottom: 10,
  },
});

export default Verification;

Expected result: Entering the SMS code and pressing Verify calls your backend. The UI shows a success or failure message based on the HTTP response.

Step 4 - Create the backend endpoints (register + verify)

Goal: Implement the server logic that stores a user in MongoDB, generates a 6-digit code, texts it via Twilio, and then verifies that code.

What to do: In your Node/Express backend, connect to MongoDB with Mongoose, initialize the Twilio client, then add:

  • POST /register to create the user, generate the code, send SMS, and store the code
  • POST /verify to compare the submitted code with the saved one

Code:

const mongoose = require('mongoose');
const twilio = require('twilio');
const express = require('express');

const config = require('./config');
const User = require('./models/User.js');

mongoose.connect(config.mongoURI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

const db = mongoose.connection;
const twilioClient = twilio(config.twilioAccountSID, config.twilioAuthToken);


db.on('error', console.error.bind(console, 'MongoDB connection error:'));
db.once('open', () => {
  console.log('Connected to MongoDB');
});

const app = express();
app.use(express.json());

app.post('/register', async (req, res) => {
  try {
    const user = new User({
      name: req.body.name,
      password: req.body.password,
      phoneNumber: req.body.phoneNumber,
    });
    await user.save();

    const verificationCode = generateRandomCode();

    await twilioClient.messages.create({
      body: `Your verification code is: ${verificationCode}`,
      from: '+18444361959',
      to: req.body.phoneNumber,
    });

    user.verificationCode = verificationCode;
    await user.save();

    res.status(201).json({ message: 'User registered successfully' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Internal server error' });
  }
});


app.post('/verify', async (req, res) => {
  try {
    const phoneNumber = req.body.phoneNumber;
    const verificationCode = req.body.verificationCode;

    const user = await User.findOne({ phoneNumber });
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    if (user.verificationCode === verificationCode) {
      res.status(200).json({ message: 'Verification successful' });
    } else {
      res.status(400).json({ error: 'Verification code is incorrect' });
    }
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Internal server error' });
  }
});


function generateRandomCode() {
  return Math.floor(100000 + Math.random() * 900000).toString();
}


const port = 3000;
app.use(express.json());

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Expected result: Registering a user triggers an SMS with a 6-digit code. Submitting the code to /verify returns success (200) when it matches, or a failure response when it does not.

Step 5 - Review the reference project

Goal: Compare your implementation to the working reference and copy any missing pieces (models, config, navigation).

What to do: Use the full project source here: https://github.com/shillehbean/youtube-p2/tree/main/twilio_text_verification_app.

Expected result: Your frontend screens and backend endpoints match the reference flow and work end to end.

Conclusion

You just implemented a basic MERN registration flow with Twilio SMS phone number verification, including a React Native (Expo) registration screen, a verification screen, and Node/Express endpoints to register and verify users.

Want to support more builds and grab parts for your next project? Shop at ShillehTek.com. If you want help integrating verification cleanly into a production app or designing a secure onboarding flow, check out our consulting services.

Subscribe: https://www.youtube.com/@mmshilleh

Support: https://www.buymeacoffee.com/mmshilleh