For Developers

Traversing the Modules File Structure for a Class

Just want the code? Skip to the end.

This example might be useful for:

  • An AI application that wants to train on all the content associated with a class
  • Building custom content viewers or navigators, like a content-aware AI search helper
  • Migrating content between different classes or systems
  • Analyzing course structure and organization
  • Automated content auditing or validation

If you're building your hot new AI learning assistant and you want it to incorporate all the course materials associated with the class a student is taking, you're going to need some way of feeding all that content into your model. And the first step to doing that is creating the master list of what content is actually associated with the class, which is precisely what this article demonstrates.

Summary

In order to fully traverse all the content associated with a given class, you must recursively request /resources from each of the class's root-level modules. Thus at a high level the process is:

  1. Get the list of root-level modules
  2. Request resources for each of them
  3. If any resource is of type 'module', then request it's resources.
  4. Repeat step 3 until all sub-modules have been requested.

Detailed Explanation

The code above demonstrates how to navigate through a hierarchical module structure in learning management systems (LMS) via the Ed.link API. Learning platforms like Canvas, Schoology, or Blackboard typically organize content in nested modules, similar to folders in a file system. This hierarchical organization can be complex to traverse programmatically.

API Endpoints and Authentication

The script uses two main Ed.link API endpoints:

  • /my/classes/:CLASS_UUID/modules - Returns the top-level modules for a specific class
  • /my/classes/:CLASS_UUID/modules/:MODULE_UUID/resources - Returns the resources contained within a specific module

Authentication is handled through a bearer token passed in the HTTP headers. This token must have appropriate permissions to access the class's content.

The Module-Resource Relationship

In LMS platforms, a module is a container that can hold various types of content. The Ed.link API represents this with an important distinction:

  • Modules are containers that can hold resources
  • Resources are items within modules, which can be:
    • Files (PDFs, images, documents)
    • Pages (HTML content)
    • Links (URLs to external content)
    • Assignments, quizzes, or discussions
    • Other modules (sub-modules)

The ID vs Target_ID Distinction

One of the most critical aspects of the code is the distinction between id and target_id when dealing with sub-modules:

  • When a module appears as a resource within another module, it has two identifiers:
    • id: Identifies the resource entry itself (the reference to the module)
    • target_id: Identifies the actual module being referenced

Using the wrong ID when making API calls will result in errors or incomplete data. This is why the code explicitly uses resource.target_id when recursively traversing sub-modules.

Recursive Traversal Logic

The heart of the script is the traverse function that implements a depth-first search algorithm:

  1. For a given module, it logs the module's title and ID
  2. It fetches all resources within that module
  3. For each resource, it checks the type:
    • If it's a module, it recursively calls itself with the sub-module's information
    • If it's any other type of resource, it simply logs its title
  4. If a module contains no resources, it logs "Empty module"

The layers parameter keeps track of the nesting depth and is used to create a visual tree structure in the output, making the hierarchy clear to the user.

The Code

Save this in a example.js file and fill in the variables at the top.

const CLASS_UUID = '00000000-your-uuid-here-000000000000';
const EDLINK_USER_TOKEN = 'your_user_bearer_token_here';

// request the root level modules for the class
const top_level_modules = await fetch(`https://ed.link/api/v2/my/classes/${CLASS_UUID}/modules`, { headers: { Authorization: `Bearer ${EDLINK_USER_TOKEN}` } }).then((r) => r.json());

// helper function for pretty printing
const padding = (n) => {
    let str = '';
    for (let i = 0; i < n; i++) {
        str += '├---';
    }
    return str;
};

// the recursive traversal logic
// the key is that `/resources` returns either sub-modules, or other things (files, pages, etc)
// if it's a sub-module, just request the resourced for it using `target_id`
//    NOT `id` since `id` represents the sub-module *as a resource* but `target_id` represents it *as a module*
const traverse = async (module_title, module_id, layers = 0) => {
    console.log(`${padding(layers)} ${module_title} - ${module_id}`);
    const module_resources = await fetch(`https://ed.link/api/v2/my/classes/${CLASS_UUID}/modules/${module_id}/resources`, { headers: { Authorization: `Bearer ${EDLINK_USER_TOKEN}` } }).then((r) =>
        r.json()
    );
    for (const resource of module_resources['$data']) {
        if (resource.type === 'module') {
            // IMPORTANT: use `target_id` here, not `id`
            await traverse(resource.title, resource.target_id, layers + 1);
        } else {
            console.log(`${padding(layers + 1)} ${resource.title}`);
        }
    }
    if (module_resources['$data'].length === 0) {
        console.log(`${padding(layers + 1)} Empty module`);
    }
};

// traverse each root-level module
for (const module of top_level_modules['$data']) {
    await traverse(module.title, module.id);
}

Then, in the terminal, just run:

node example.js

And you should see something like this which mirrors your class's module structure:

 Files Module - 0b083889-c8e5-4264-9dcb-ece928583fcc
├--- Screenshot.png
├--- mytest.txt
├--- SomePage.html
├--- filesubmod - c6764b45-d288-4962-80e0-2b0fbf955c61
├---├--- mytest_sub1.txt
├---├--- mytest_sub2.txt
├---├--- third level submodule - 9ecea43a-8114-41bc-aa29-ee757f64768e
├---├---├--- Empty module
├--- basicpage1.html
├--- google.com
 Second Module - 024842b7-d80b-4d01-8bb2-6cd9b969025c
├--- mytest.txt