React Integration

Learn how to integrate NestSMS into your React application for sending SMS messages through your backend API.

Setup Backend Proxy

First, create a backend API endpoint that will handle SMS requests:

Node.js/Express Backend

// server.js
const express = require("express");
const cors = require("cors");
require("dotenv").config();

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

const NESTSMS_API_KEY = process.env.NESTSMS_API_KEY;

app.post("/api/sms/send", async (req, res) => {
  try {
    const { to, message } = req.body;

    const response = await fetch("https://api.nestsms.com/api/v1/sms/send", {
      method: "POST",
      headers: {
        "X-API-Key": NESTSMS_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        to,
        message,
        sender_id: "YourBrand",
        message_type: "transactional",
      }),
    });

    const data = await response.json();
    res.json(data);
  } catch (error) {
    res.status(500).json({ success: false, error: error.message });
  }
});

app.listen(3001, () => console.log("Backend running on port 3001"));

React Components

SMS Form Component

// components/SMSForm.tsx
import { useState } from "react";

export function SMSForm() {
  const [phone, setPhone] = useState("");
  const [message, setMessage] = useState("");
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState<any>(null);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setResult(null);

    try {
      const response = await fetch("http://localhost:3001/api/sms/send", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ to: phone, message }),
      });

      const data = await response.json();
      setResult(data);

      if (data.success) {
        setPhone("");
        setMessage("");
      }
    } catch (error) {
      setResult({
        success: false,
        error: "Failed to send SMS. Please try again.",
      });
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="mx-auto max-w-md p-6">
      <h2 className="mb-4 text-2xl font-bold">Send SMS</h2>

      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label className="mb-1 block text-sm font-medium">Phone Number</label>
          <input
            type="tel"
            value={phone}
            onChange={(e) => setPhone(e.target.value)}
            placeholder="9840123456"
            required
            className="w-full rounded-lg border px-3 py-2"
          />
        </div>

        <div>
          <label className="mb-1 block text-sm font-medium">Message</label>
          <textarea
            value={message}
            onChange={(e) => setMessage(e.target.value)}
            placeholder="Your message here..."
            required
            maxLength={720}
            rows={4}
            className="w-full rounded-lg border px-3 py-2"
          />
          <p className="mt-1 text-sm text-gray-500">{message.length}/720 characters</p>
        </div>

        <button
          type="submit"
          disabled={loading}
          className="bg-primary hover:bg-primary/80 w-full rounded-lg py-2 text-white disabled:bg-gray-400"
        >
          {loading ? "Sending..." : "Send SMS"}
        </button>
      </form>

      {result && (
        <div
          className={`mt-4 rounded-lg p-4 ${
            result.success ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800"
          }`}
        >
          {result.success ? (
            <>
              <p className="font-semibold">✓ SMS sent successfully!</p>
              <p className="mt-1 text-sm">Job ID: {result.job_id}</p>
            </>
          ) : (
            <p>✗ Error: {result.error}</p>
          )}
        </div>
      )}
    </div>
  );
}

Custom Hook for SMS

// hooks/useSMS.ts
import { useState } from "react";

interface SendSMSParams {
  to: string;
  message: string;
}

interface SMSResult {
  success: boolean;
  job_id?: string;
  error?: string;
}

export function useSMS() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const sendSMS = async ({ to, message }: SendSMSParams): Promise<SMSResult> => {
    setLoading(true);
    setError(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) {
        setError(data.error || "Failed to send SMS");
      }

      return data;
    } catch (err) {
      const errorMessage = "Network error. Please try again.";
      setError(errorMessage);
      return { success: false, error: errorMessage };
    } finally {
      setLoading(false);
    }
  };

  return { sendSMS, loading, error };
}

Using the Custom Hook

// components/SMSFormWithHook.tsx
import { useState } from "react";
import { useSMS } from "../hooks/useSMS";

export function SMSFormWithHook() {
  const [phone, setPhone] = useState("");
  const [message, setMessage] = useState("");
  const { sendSMS, loading, error } = useSMS();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    const result = await sendSMS({ to: phone, message });

    if (result.success) {
      alert("SMS sent successfully!");
      setPhone("");
      setMessage("");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="tel"
        value={phone}
        onChange={(e) => setPhone(e.target.value)}
        placeholder="9840123456"
        required
      />

      <textarea
        value={message}
        onChange={(e) => setMessage(e.target.value)}
        placeholder="Your message"
        required
      />

      <button type="submit" disabled={loading}>
        {loading ? "Sending..." : "Send SMS"}
      </button>

      {error && <p className="error">{error}</p>}
    </form>
  );
}

OTP Verification Flow

OTP Component

// components/OTPVerification.tsx
import { useState } from "react";

export function OTPVerification() {
  const [phone, setPhone] = useState("");
  const [otp, setOtp] = useState("");
  const [step, setStep] = useState<"phone" | "verify">("phone");
  const [loading, setLoading] = useState(false);

  const sendOTP = async () => {
    setLoading(true);
    try {
      const response = await fetch("http://localhost:3001/api/otp/send", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ phone }),
      });

      const data = await response.json();

      if (data.success) {
        setStep("verify");
      }
    } catch (error) {
      alert("Failed to send OTP");
    } finally {
      setLoading(false);
    }
  };

  const verifyOTP = async () => {
    setLoading(true);
    try {
      const response = await fetch("http://localhost:3001/api/otp/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ phone, otp }),
      });

      const data = await response.json();

      if (data.success) {
        alert("OTP verified successfully!");
        // Proceed with authentication
      } else {
        alert("Invalid OTP");
      }
    } catch (error) {
      alert("Verification failed");
    } finally {
      setLoading(false);
    }
  };

  if (step === "phone") {
    return (
      <div>
        <h3>Enter Phone Number</h3>
        <input
          type="tel"
          value={phone}
          onChange={(e) => setPhone(e.target.value)}
          placeholder="9840123456"
        />
        <button onClick={sendOTP} disabled={loading}>
          {loading ? "Sending..." : "Send OTP"}
        </button>
      </div>
    );
  }

  return (
    <div>
      <h3>Enter OTP</h3>
      <p>We sent a code to {phone}</p>
      <input
        type="text"
        value={otp}
        onChange={(e) => setOtp(e.target.value)}
        placeholder="123456"
        maxLength={6}
      />
      <button onClick={verifyOTP} disabled={loading}>
        {loading ? "Verifying..." : "Verify OTP"}
      </button>
      <button onClick={() => setStep("phone")}>Change Number</button>
    </div>
  );
}

Using React Query

// hooks/useSMSQuery.ts
import { useMutation } from "@tanstack/react-query";

interface SendSMSParams {
  to: string;
  message: string;
}

export function useSendSMS() {
  return useMutation({
    mutationFn: async ({ to, message }: SendSMSParams) => {
      const response = await fetch("http://localhost:3001/api/sms/send", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ to, message }),
      });

      if (!response.ok) {
        throw new Error("Failed to send SMS");
      }

      return response.json();
    },
  });
}

// Usage in component
function SMSComponent() {
  const sendSMS = useSendSMS();

  const handleSend = () => {
    sendSMS.mutate(
      { to: "9840123456", message: "Hello!" },
      {
        onSuccess: (data) => {
          console.log("SMS sent:", data.job_id);
        },
        onError: (error) => {
          console.error("Error:", error);
        },
      },
    );
  };

  return (
    <button onClick={handleSend} disabled={sendSMS.isPending}>
      {sendSMS.isPending ? "Sending..." : "Send SMS"}
    </button>
  );
}

Best Practices

  1. Never expose API keys in React 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. Add rate limiting on the backend

Next Steps