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

  1. Never expose API keys in Vue code
  2. Always use a backend proxy for API calls
  3. Implement proper error handling
  4. Add loading states for better UX
  5. Validate phone numbers before sending
  6. Show clear success/error messages
  7. Use composables for reusable logic (Vue 3)

Next Steps