Vue Integration
Learn how to integrate NestSMS into your Vue.js application for sending SMS messages through your backend API.
Setup Backend Proxy
First, create a backend API endpoint (see Node.js integration for backend setup).
Vue 3 Composition API
SMS Component
<!-- components/SMSForm.vue -->
<template>
<div class="sms-form">
<h2>Send SMS</h2>
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label for="phone">Phone Number</label>
<input id="phone" v-model="phone" type="tel" placeholder="9840123456" required />
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea
id="message"
v-model="message"
placeholder="Your message here..."
required
maxlength="720"
rows="4"
></textarea>
<small>{{ message.length }}/720 characters</small>
</div>
<button type="submit" :disabled="loading">
{{ loading ? "Sending..." : "Send SMS" }}
</button>
</form>
<div v-if="result" :class="['alert', result.success ? 'success' : 'error']">
<template v-if="result.success">
<p><strong>ā SMS sent successfully!</strong></p>
<p>Job ID: {{ result.job_id }}</p>
</template>
<template v-else>
<p>ā Error: {{ result.error }}</p>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const phone = ref("");
const message = ref("");
const loading = ref(false);
const result = ref<any>(null);
const handleSubmit = async () => {
loading.value = true;
result.value = null;
try {
const response = await fetch("http://localhost:3001/api/sms/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
to: phone.value,
message: message.value,
}),
});
const data = await response.json();
result.value = data;
if (data.success) {
phone.value = "";
message.value = "";
}
} catch (error) {
result.value = {
success: false,
error: "Failed to send SMS. Please try again.",
};
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.sms-form {
max-width: 500px;
margin: 0 auto;
padding: 2rem;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
input,
textarea {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
width: 100%;
padding: 0.75rem;
background: #3b82f6;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #9ca3af;
cursor: not-allowed;
}
.alert {
margin-top: 1rem;
padding: 1rem;
border-radius: 4px;
}
.alert.success {
background: #d1fae5;
color: #065f46;
}
.alert.error {
background: #fee2e2;
color: #991b1b;
}
</style>Composable for SMS
// composables/useSMS.ts
import { ref } from "vue";
interface SendSMSParams {
to: string;
message: string;
}
interface SMSResult {
success: boolean;
job_id?: string;
error?: string;
}
export function useSMS() {
const loading = ref(false);
const error = ref<string | null>(null);
const sendSMS = async ({ to, message }: SendSMSParams): Promise<SMSResult> => {
loading.value = true;
error.value = null;
try {
const response = await fetch("http://localhost:3001/api/sms/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ to, message }),
});
const data = await response.json();
if (!data.success) {
error.value = data.error || "Failed to send SMS";
}
return data;
} catch (err) {
const errorMessage = "Network error. Please try again.";
error.value = errorMessage;
return { success: false, error: errorMessage };
} finally {
loading.value = false;
}
};
return { sendSMS, loading, error };
}Using the Composable
<!-- components/SMSFormWithComposable.vue -->
<template>
<form @submit.prevent="handleSubmit">
<input v-model="phone" type="tel" placeholder="9840123456" required />
<textarea v-model="message" placeholder="Your message" required></textarea>
<button type="submit" :disabled="loading">
{{ loading ? "Sending..." : "Send SMS" }}
</button>
<p v-if="error" class="error">{{ error }}</p>
</form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { useSMS } from "../composables/useSMS";
const phone = ref("");
const message = ref("");
const { sendSMS, loading, error } = useSMS();
const handleSubmit = async () => {
const result = await sendSMS({
to: phone.value,
message: message.value,
});
if (result.success) {
alert("SMS sent successfully!");
phone.value = "";
message.value = "";
}
};
</script>OTP Verification Flow
<!-- components/OTPVerification.vue -->
<template>
<div class="otp-verification">
<div v-if="step === 'phone'">
<h3>Enter Phone Number</h3>
<input v-model="phone" type="tel" placeholder="9840123456" />
<button @click="sendOTP" :disabled="loading">
{{ loading ? "Sending..." : "Send OTP" }}
</button>
</div>
<div v-else>
<h3>Enter OTP</h3>
<p>We sent a code to {{ phone }}</p>
<input v-model="otp" type="text" placeholder="123456" maxlength="6" />
<button @click="verifyOTP" :disabled="loading">
{{ loading ? "Verifying..." : "Verify OTP" }}
</button>
<button @click="step = 'phone'">Change Number</button>
</div>
<div v-if="message" :class="['message', messageType]">
{{ message }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const phone = ref("");
const otp = ref("");
const step = ref<"phone" | "verify">("phone");
const loading = ref(false);
const message = ref("");
const messageType = ref<"success" | "error">("success");
const sendOTP = async () => {
loading.value = true;
message.value = "";
try {
const response = await fetch("http://localhost:3001/api/otp/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone: phone.value }),
});
const data = await response.json();
if (data.success) {
step.value = "verify";
message.value = "OTP sent successfully!";
messageType.value = "success";
} else {
message.value = data.error || "Failed to send OTP";
messageType.value = "error";
}
} catch (error) {
message.value = "Network error. Please try again.";
messageType.value = "error";
} finally {
loading.value = false;
}
};
const verifyOTP = async () => {
loading.value = true;
message.value = "";
try {
const response = await fetch("http://localhost:3001/api/otp/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ phone: phone.value, otp: otp.value }),
});
const data = await response.json();
if (data.success) {
message.value = "OTP verified successfully!";
messageType.value = "success";
// Proceed with authentication
} else {
message.value = data.error || "Invalid OTP";
messageType.value = "error";
}
} catch (error) {
message.value = "Verification failed. Please try again.";
messageType.value = "error";
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.otp-verification {
max-width: 400px;
margin: 0 auto;
}
.message {
margin-top: 1rem;
padding: 0.75rem;
border-radius: 4px;
}
.message.success {
background: #d1fae5;
color: #065f46;
}
.message.error {
background: #fee2e2;
color: #991b1b;
}
</style>Vue 2 Options API
<!-- components/SMSForm.vue (Vue 2) -->
<template>
<div class="sms-form">
<h2>Send SMS</h2>
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label for="phone">Phone Number</label>
<input id="phone" v-model="phone" type="tel" placeholder="9840123456" required />
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea
id="message"
v-model="message"
placeholder="Your message here..."
required
maxlength="720"
rows="4"
></textarea>
<small>{{ message.length }}/720 characters</small>
</div>
<button type="submit" :disabled="loading">
{{ loading ? "Sending..." : "Send SMS" }}
</button>
</form>
<div v-if="result" :class="['alert', result.success ? 'success' : 'error']">
<template v-if="result.success">
<p><strong>ā SMS sent successfully!</strong></p>
<p>Job ID: {{ result.job_id }}</p>
</template>
<template v-else>
<p>ā Error: {{ result.error }}</p>
</template>
</div>
</div>
</template>
<script>
export default {
name: "SMSForm",
data() {
return {
phone: "",
message: "",
loading: false,
result: null,
};
},
methods: {
async handleSubmit() {
this.loading = true;
this.result = null;
try {
const response = await fetch("http://localhost:3001/api/sms/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
to: this.phone,
message: this.message,
}),
});
const data = await response.json();
this.result = data;
if (data.success) {
this.phone = "";
this.message = "";
}
} catch (error) {
this.result = {
success: false,
error: "Failed to send SMS. Please try again.",
};
} finally {
this.loading = false;
}
},
},
};
</script>Using Axios in Vue
// services/smsService.ts
import axios from "axios";
const API_URL = "http://localhost:3001/api";
export const smsService = {
async send(to: string, message: string) {
try {
const response = await axios.post(`${API_URL}/sms/send`, {
to,
message,
});
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || "Failed to send SMS");
}
throw error;
}
},
async checkStatus(messageId: string) {
try {
const response = await axios.get(`${API_URL}/sms/status/${messageId}`);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(error.response?.data?.error || "Failed to check status");
}
throw error;
}
},
};Nuxt 3 Integration
<!-- pages/sms.vue (Nuxt 3) -->
<template>
<div>
<h2>Send SMS</h2>
<form @submit.prevent="handleSubmit">
<input v-model="phone" type="tel" placeholder="9840123456" required />
<textarea v-model="message" placeholder="Your message" required></textarea>
<button type="submit" :disabled="pending">
{{ pending ? "Sending..." : "Send SMS" }}
</button>
</form>
<div v-if="data" class="result">
{{ data.success ? "SMS sent!" : data.error }}
</div>
</div>
</template>
<script setup lang="ts">
const phone = ref("");
const message = ref("");
const { data, pending, execute } = await useFetch("/api/sms/send", {
method: "POST",
immediate: false,
body: computed(() => ({
to: phone.value,
message: message.value,
})),
});
const handleSubmit = async () => {
await execute();
if (data.value?.success) {
phone.value = "";
message.value = "";
}
};
</script>Best Practices
- Never expose API keys in Vue code
- Always use a backend proxy for API calls
- Implement proper error handling
- Add loading states for better UX
- Validate phone numbers before sending
- Show clear success/error messages
- Use composables for reusable logic (Vue 3)