Eric Bergman-Terrell's Blog

Node.js / Express Programming Tip: How to Implement CAPTCHA
September 22, 2015

use captcha to identify bots
Robot or human? Let CAPTCHA decide!

If your website has forms that users can fill in and submit, automatic "robots" or "bots" will be filling them in too, hoping to post their dubious content on your website. You can prevent this with Google's reCAPTCHA service. This blog post will show you how to incorporate CAPTCHA into your Node.js / Express website. This post assumes you use EJS templates to render HTML. If you use a different template engine, you'll just need to change create_comment.ejs and partials/recaptchaDiv.ejs accordingly (see below).

This website uses CAPTCHA in a few places, for instance, the form that users fill out to submit blog comments. Sometimes the Google reCAPTCHA service will simply trust that you are "not a robot" when you check the checkbox. Other times you will be prompted to identify a certain sort of picture. The last time I filled out this form, Google took my word:


Here's the EJS template for the above form:


<% layout('../layout') -%>
<% block('scripts', '<script src="" async defer></script>') -%>
<% block('scripts', '<script type="text/javascript" src="/javascripts/jquery-validate.min.js"></script>') -%>
<% block('scripts', '<script type="text/javascript" src="/javascripts/gotofirsttextbox.min.js"></script>') -%>

<h1><%= title %></h1>

<% if (captchaError) { %>
<div class="error">CAPTCHA error, please try again.</div>
<% } %>

<form role="form" action="/blog/create_comment/<%= id %>" method="POST">
    <%-partial('../partials/recaptchaDiv.ejs') %>

    <table width="100%">
            <col span="1" style="width: 35%;">
            <col span="1" style="width: 65%;">

        <tr colspan="2" style="height: 2em;" ></tr>
            <td><label for="Name">Name: (*)</label></td>
            <td><input type="text" id="Name" name="Name" required/></td>
            <td><label for="EmailAddress">Email Address (will not be published): (*)</label></td>
            <td><input type="email" id="EmailAddress" name="EmailAddress" required/></td>
            <td><label for="URL">URL:</label></td>
            <td><input type="text" id="URL" name="URL" style="width: 100%"/></td>
            <td valign="top"><label for="Comments">Comments: (*)</label></td>
            <td><textarea name="Comments" id="Comments" rows="10" style="width: 100%" required></textarea></td>
        <tr colspan="2" style="height: 1em;" ></tr>

    <button type="submit">Submit</button>

... which uses this partial view:


<div class="g-recaptcha" data-sitekey="<%=partials.recaptchaPublicKey%>"></div>

The partials.recaptchaPublicKey value will be provided by a middleware function which you'll see later in this post.

This form does the user a small favor and focuses the cursor on the first textbox. Why don't all web forms do this?

gotofirsttextbox.js: (of course, this website serves up the minified version of this file)

$(document).ready(function () {

After you sign up for the Google reCAPTCHA service, store your public and private keys where your Javascript code can access them:

exports.captchaConfig = {
    publicKey: "{public key}",
    privateKey: "{private key}"

The following code validates the form post. It's based on this blog post.


var https = require('https');
var config = require('./config');

// Helper function to make API call to recatpcha and check response
exports.verifyRecaptcha = function verifyRecaptcha(key, callback) {
    https.get("" + config.captchaConfig.privateKey + "&response=" + key, function(res) {
        var data = "";
        res.on('data', function (chunk) {
            data += chunk.toString();
        res.on('end', function() {
            try {
                var parsedData = JSON.parse(data);
            } catch (e) {

Code to serve up the form for an HTTP GET:

function setupCreateCommentGet(app) {
    app.get('/blog/create_comment/:id', function (req, res) {
                title: 'Create Blog Comment',
                captchaError: false,

Code to process an HTTP POST to the form:

function setupCreateCommentPost(app) {'/blog/create_comment/:id', function (req, res) {
        captcha.verifyRecaptcha(req.body["g-recaptcha-response"], function (success) {
            if (success) {

                blogCommentModel.insert(comment, function (error, result) {
                    if (!error) {
                                title: 'Your Blog Comment Has Been Submitted'
                    else {
                        global.logger.error('Error inserting blog comment: ' + error);
            } else {
                res.render('../views/blog/create_comment', {title: 'Contact', captchaError: true});

I mentioned earlier that partials/recaptchaDiv.ejs relies on a partials.recaptchaPublicKey value that is made available by a middleware function. requestHook is that function. requestHook will provide that value for any GET or POST:


exports.requestHook = function(req, res, next) {
    if (req.method === 'GET' || req.method === 'POST') {
        if (!res.locals.partials) {
            res.locals.partials = {};

        res.locals.partials.recaptchaPublicKey = config.captchaConfig.publicKey;

    // keep executing the router middleware


The main application hooks in the middleware function as follows:

var requestHook = require('./libs/requestHook');


Note, if you don't want to bother with the middleware function, there is a much simpler alternative. Just update partials/recaptchaDiv.ejs and enter your public key in-line.

it this human? only CAPTCHA knows for sure
It might be human...

Keywords: Node.js, node, Express, CAPTCHA, reCAPTCHA, robots, bots, Javascript, forms, HTTP, GET, POST, middleware

Reader Comments

Comment on this Blog Post

Recent Posts

Python Script to Audit MediaMonkey TranscodingAugust 15, 2019
How to decompile Java code with JetBrains IntelliJ IDEA (2018.2.3, Windows 10)October 5, 2018
Java Programming Tip: SWT Photo Frame ProgramOctober 31, 2016
Vault 3 (Desktop) Version 1.63 ReleasedSeptember 9, 2016
"Compliance with Court Orders Act of 2016"April 9, 2016
Disable "Visual Voicemail" on Android / T-MobileJanuary 17, 2016
IPv6 HumorDecember 10, 2015