Back to blog

Friday, April 4, 2025

Cloudflare Turnstile Integration with NestJS and Next.js

cover

Integrating Cloudflare Turnstile in NestJS and Next.js

Introduction

CAPTCHAs have long been a necessary safeguard against spam and bot traffic, but they often introduce friction into the user experience. Cloudflare Turnstile offers a modern, privacy-focused, CAPTCHA-free alternative that allows you to verify users seamlessly. In this guide, we'll walk through how to integrate Cloudflare Turnstile with a NestJS backend and a Next.js frontend.enter image description here

Why Use Cloudflare Turnstile?

  1. No User Friction – Users are verified without solving puzzles.
  2. Privacy-First – Does not track or fingerprint users. Seamless
  3. Integration – Designed to work easily with multiple frameworks.
  4. Server-Side Validation – Ensures security against token spoofing.

Flow Diagram: How Turnstile Works

enter image description here

Steps to Integrate in Next and Nest Js.

Step 1: Set Up Cloudflare Turnstile

  • Go to Cloudflare Turnstile and register your site.
  • Retrieve your Site Key and Secret Key.
  • Choose a widget mode (e.g., Managed, Non-interactive, or Invisible). enter image description here

Step 2: Frontend – Next.js Integration

  • Install the Turnstile React Package npm install react-turnstile
  • Add Environment Variables Create a .env.local in your Next.js root:
NEXT_PUBLIC_TURNSTILE_SITE_KEY=your-site-key
NEXT_PUBLIC_API_URL=http://localhost:3000/api
  • Create the Turnstile Component
import React, { useState } from "react";
import Turnstile from "react-turnstile";
interface Props {
  onVerify: (token: string) => void;
}
const TurnstileComponent: React.FC<Props> = ({ onVerify }) => {
  const [token, setToken] = useState("");
  return (
    <div>
      <Turnstile
        sitekey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!}
        onVerify={(token) => {
          setToken(token);
          onVerify(token);
        }}
      />
    </div>
  );
};
export default TurnstileComponent;

  • Use the Component in a Form
import React, { useState } from "react";
import TurnstileComponent from "../components/TurnstileComponent";
const Home = () => {
 const [token, setToken] = useState("");
 const [message, setMessage] = useState("");
 const handleSubmit = async (e: React.FormEvent) => {
   e.preventDefault();
   const response = await fetch(
     "${process.env.NEXT_PUBLIC_API_URL}/auth/verify-turnstile",
     {
       method: "POST",
       headers: { "Content-Type": "application/json" },
       body: JSON.stringify({ token }),
     },
   );
   const data = await response.json();
   setMessage(data.message);
 };
 return (
   <form onSubmit={handleSubmit}>
     <TurnstileComponent onVerify={setToken} />
     <button type="submit">Submit</button>
     {message && <p>{message}</p>}
   </form>
 );
};
export default Home;

Step 3: Backend – NestJS Verification

  • Install Dependencies npm install axios dotenv
  • Add Turnstile Secret Key
  TURNSTILE_SECRET_KEY=your-secret-key
  • Create a Verification Service
import { Injectable } from "@nestjs/common";
import axios from "axios";
@Injectable()
export class TurnstileService {
 async verifyToken(token: string): Promise<boolean> {
   const secretKey = process.env.TURNSTILE_SECRET_KEY;
   const response = await axios.post(
     "https://challenges.cloudflare.com/turnstile/v0/siteverify",
     new URLSearchParams({ secret: secretKey, response: token }),
     { headers: { "Content-Type": "application/x-www-form-urlencoded" } },
   );
   return response.data.success;
 }
}   
  • Add an Auth Controller
import { Injectable } from "@nestjs/common";
import axios from "axios";
@Injectable()
export class TurnstileService {
  async verifyToken(token: string): Promise<boolean> {
    const secretKey = process.env.TURNSTILE_SECRET_KEY;
    const response = await axios.post(
      "https://challenges.cloudflare.com/turnstile/v0/siteverify",
      new URLSearchParams({ secret: secretKey, response: token }),
      { headers: { "Content-Type": "application/x-www-form-urlencoded" } },
    );
    return response.data.success;
  }
}

Step 4: Run and Test

  • Start the backend: npm run start:dev
  • Start the frontend: npm run dev
  • Submit the form and check verification status.

Cloudflare Turnstile vs. Google reCAPTCHA

CategoryCloudflare TurnstileGoogle reCAPTCHA
User ExperienceNo puzzles; seamlessMay show puzzles or checkbox
PrivacyDoes not track usersBehavioral tracking and cookies
GDPR ComplianceMore transparent and compliantOften questioned under GDPR
IntegrationSimple, modern APIRequires integration into Google stack
Invisible OptionYes (managed widget)Yes (invisible v2/v3)
CustomizationHighly customizableLimited customization
SecurityStrong bot detectionStrong, but with behavioral scoring
Rate LimitsGenerous and freeUsage quotas apply

Summary

  • Use Cloudflare Turnstile if you value privacy, simplicity, and a frictionless user experience.
  • Use Google reCAPTCHA if you're already integrated into the Google ecosystem and require more complex risk analysis with established infrastructure.

Conclusion

Cloudflare Turnstile provides a user-friendly and privacy-conscious approach to bot prevention. By integrating Turnstile into your NestJS and Next.js applications, you can enhance security without sacrificing user experience. This setup is particularly effective for protecting login forms, contact submissions, and other sensitive user interactions.Integrating Cloudflare Turnstile in NestJS and Next.js For further improvements, Turnstile verification can be wrapped into guards or middleware to protect specific routes or endpoints throughout the application. If you need additional help implementing this in production or customizing it to your architecture, feel free to reach us out at [email protected].

this is good