Get free Udemy courses with Nightmare.js, Part 1

Udemy courses are a great way to learn a new skill, and there are a lot of courses on Udemy available free of charge. Unfortunately, there’s not an easy way to add all of the freely available Udemy courses to your Udemy library. To fix that I decided to make a Node.js console application that could query the Udemy API for a list of free courses and iterate over that list using a browser automation library like Nightmare.js. To get started I first had to request an API key from

Then I all I had to do was use Axios to make a get request to the Udemy API. Axios is a promise-based HTTP client that can run in the browser or in a Node.js script. To install it, all I had to do was a yarn command to install Axios. Then I simply had to create a module to get the list of courses. I didn’t want courses in any language other than English, but in order to make this script reusable I pulled out the variable that determines language into a module property so that anyone could easily change the language they are querying for. Unfortunately, the Udemy API only allows you to get 100 course objects at a time, so I couldn’t make just one get request. In order to get around this limitation, I added a recursive function call to the promise resolution callback. That way, when we get the first 100 items, we can immediately make another get request for the next page. You can see the module below:

const fs = require('fs');
const path = require('path');
const ax = require('axios');
const secrets = require('./secrets.js');

var ApiModule = module.exports = {
    _count: 0,
    _courses: [],
    _path: 'courses.json',
    _pageNumber: 0,
    _pageSize: 100, // Max page size is 100.
    _languageCode: 'en',
    _secrets: require(path.resolve(__dirname, 'secrets.js')),
    _url: '',
    getCourses: function() {
        let finalUrl = `${this._url}?page=${this._pageNumber+1}&page_size=${this._pageSize}&price=price-free&language=${this._languageCode}`;

        return ax.get(finalUrl, {
            headers: { 'Authorization': secrets.authHeader }
        .then(function (res) {
            if (this._count === 0) {
                this._count =;
                this._courses = this._courses.concat(;
            else {
                this._count = this._count - this._pageSize;
                this._courses = this._courses.concat(;
            if (this._count > 0) {
                return this.getCourses();
            else {
                return this._courses;
        .catch(function (err) {
            if (err) { console.log('Cannot get course data!'); throw err; }

After I got the courses I had to find a way to script out the steps necessary to enroll in each of the courses. Originally, I tried Nightwatch.js, a test runner that uses Selenium to automate browser functions. However, Nightwatch was a little bit error prone, and I didn’t like the necessity of assertions and Selenium in my initial script. Nightmare.js, however, is a generic browser automation tool that uses Electron as its browser instead of a need for a web driver from Selenium. It also uses Promises instead of callback functions to handle asynchronous operations. All I had to do to get Nightmare installed was a yarn add command for nightmare. The initial install might take awhile, though, because nightmare depends on electron and it has to build a fresh copy. The module I created to run automation can be found below:

const Nightmare = require('nightmare');
const nightmare = Nightmare({ show: true });
const secrets = require('./secrets');
//const expect = require('chai').expect;

var NightmareModule = module.exports = {
    _index: 0,
    enroll: function(courses) {
        courses.then(function(result) {
        .catch(function(err) {
            if (err) { console.log('Cannot enroll in courses!'); throw err; }
    automationFunction: function(result) {
        if (this._index > result.length) {
            return true;
        else {
            course = result[this._index];
            return nightmare
                .type('#id_email', secrets.username)
                .type('#id_password', secrets.password)
                .evaluate(() => {
                    return document.querySelector('.container').innerText;
                .then(function(result) {
                    if(result.includes('Congratulations! You\'ve successfully enrolled in')) {
                        console.log(`Successfully enrolled in ${course.title}`);
                        console.log('Enrolling in next course now...');
                        return this.automationFunction(result);
                    else { throw new Error(); }
                .catch(function(err) {
                    if (err) { console.log(`Couldn't enroll in ${course.title}!`);}

This would have been great if it worked, but I ran into some captchas that caused this particular script not to run. There’s also the problem of automation that relies on network resources being inherently error prone. I think it’s possible to make this particular script run the way I want it to, but it would require a lot more control over Udemy than I have currently. I’m going to open up my repository and pull requests are welcome, but I don’t know how much farther I’m going to be able to go with this script. Here’s the repository.

Side note on commander.js:

Commander is a NPM package that parses command line arguments, options, and switches for you. I really like it’s simplicity and the -h flag that is automatically built based on your configuration of the initial program variable. This allows you to delegate command line argument parsing to something that does it really well. I put a few switches into the index file of this script, but I didn’t implement them at all. I really like how easy commander makes it to do what would normally require a lot of custom code. I know that commander.js is based on Ruby’s commander gem, but I’m really glad that concepts like command line parsing have real value across programming languages.