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.
- 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
/registerand/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 /registerto create the user, generate the code, send SMS, and store the code -
POST /verifyto 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


