Node.js library that uses Bottleneck and Axios to handle concurrent calls to Canvas LMS API. Uses backoff delays if it hits 403 throttles and various calculations for efficiency.
Built in the thick of many real-world Canvas tasks. Still very much a work in progress. Not linted. Presumes you're using an admin-level API access token. No guarantees that anything will work or won't hopelessly erase all your Canvas data etc. Be adventurous; have fun; etc. Please feel free to make pull requests and improvements as needed. Big thanks to James Jones for his tips and tricks and help with speculative concurrency.
- Handles bookmarks sequentially. Speculative concurrency is hard with bookmarks on purpose because Canvas doesn't want you hogging the API, which is fair. If you want to dive in, you might start with this Canvas Community post, where James explains that bookmarks are Base64 JSON strings, and in theory if you know how your data results are being sorted, you could do speculative concurrency with bookmarks.
- getList if you need a list from one endpoint; getAllResultsFromArray if you need something like all the assignments in a list of courses (be careful about memory limits and all that if you're getting big objects like submissions)
- getSubmissions is really the only "helper" function because I found myself getting submissions so often. But you could go to town with other levels of abstraction if you want!
- Why axios and not fetch? I like axios better. Change it to fetch if you want; life is short; go see the Grand Canyon; etc.
The getList
function allows you to fetch paginated lists of items from Canvas, such as courses, assignments, or students.
const CanvasMultiCurl = require('./CanvasMultiCurl');
const canvas = new CanvasMultiCurl('YOUR_CANVAS_ACCESS_TOKEN', 'https://canvas.your-instance.com');
// Fetch all the courses in root account, 100 at a time
(async () => {
try {
const courses = await canvas.getList('accounts/1/courses');
console.log('Courses:', courses);
} catch (error) {
console.error('Error fetching courses:', error);
}
})();
const courseId = 12345; // Replace with your course ID
(async () => {
try {
const assignments = await canvas.getList(`courses/${courseId}/assignments`);
console.log('Assignments:', assignments);
} catch (error) {
console.error('Error fetching assignments:', error);
}
})();
const courseId = 12345; // Replace with your course ID
(async () => {
try {
// Use vars to filter assignments by a specific bucket and search term
const vars = `bucket=past&search_term=project`; // Example: Fetch 'past' assignments that include the word 'project'
const assignments = await canvas.getList(`courses/${courseId}/assignments`, vars);
console.log('Filtered assignments:', assignments);
} catch (error) {
console.error('Error fetching filtered assignments:', error);
}
})();
The request
function is a flexible way to make any kind of API request (GET, POST, PUT, DELETE). You can use it for actions such as updating, creating, or deleting items in Canvas.
const courseId = 12345;
const assignmentId = 67890;
const updatedData = {
assignment: {
name: "Updated Assignment Name",
description: "This is the updated assignment description."
}
};
(async () => {
try {
const response = await canvas.request(`courses/${courseId}/assignments/${assignmentId}`, 'PUT', updatedData, 'assignment');
console.log('Assignment updated:', response);
} catch (error) {
console.error('Error updating assignment:', error);
}
})();
The getAllResultsFromArray
method allows you to make multiple requests in parallel, which is useful for fetching large datasets more efficiently.
const courseIds = [12345, 67890, 11223]; // Replace with actual course IDs
(async () => {
try {
const allAssignments = await canvas.getAllResultsFromArray('accounts/1/courses/<item>/assignments', courseIds);
console.log('All assignments:', allAssignments);
} catch (error) {
console.error('Error fetching assignments for multiple courses:', error);
}
})();
This method simplifies fetching student submissions for assignments within a course, with optional filters for students and submission states.
const courseId = 12345;
const assignmentIds = [56789, 67890]; // Replace with assignment IDs
const studentIds = [13579, 24680]; // Optional: Specific student IDs
(async () => {
try {
const submissions = await canvas.getSubmissions(courseId, assignmentIds, studentIds);
console.log('Submissions:', submissions);
} catch (error) {
console.error('Error fetching submissions:', error);
}
})();
This method allows you to send different types of requests at the same time, which is useful for reducing the time spent waiting on multiple unrelated API calls.
const courseId = 12345;
const requestConfigs = [
// Fetch course details
canvas.createRequestConfig(`courses/${courseId}`, 'GET'),
// Fetch all assignments in the course
canvas.createRequestConfig(`courses/${courseId}/assignments`, 'GET'),
// Fetch all users in the course
canvas.createRequestConfig(`courses/${courseId}/users`, 'GET')
];
(async () => {
try {
const [courseDetails, assignments, users] = await canvas.handleConcurrentRequests(requestConfigs);
console.log('Course details:', courseDetails.data);
console.log('Assignments:', assignments.data);
console.log('Users:', users.data);
} catch (error) {
console.error('Error during concurrent requests:', error);
}
})();