Exercise: The NUSMods API

I'm sure you know what NUSMods is, its a website that has details about every course offered at NUS, as well as a degree planner, a timetable builder and a map of the campus. It also has its own API (documentation here) which we are going to use.

The task

The task is to build a simple page that will have a form. The form will allow a user to type in a course code, and once submitted the form will submit a fetch request to the NUSMods API, request the course data, and then display it to the user. If the course code they enter is invalid, then show them an error message.

It is recommended to try the first steps on your own, until you get to the fetch request, to practice writing HTML and JS.

Some info

The API we will use is the one to get the course info given a course code and year. The general URL format is below:

https://api.nusmods.com/v2/{acadYear}/modules/{courseCode}.json

Example:

https://api.nusmods.com/v2/2023-2024/modules/CS2030S.json

Copy paste the above URL into your browser address bar to see the response. You'll see the response body in JSON format, like below.

Note: The course code needs to be in all capital letters for the request to succeed.

For this exercise, we'll stick to the year 2023-2024.

Step 1 - Get the form set up

So the first step is to ready the form. It will have the following:

  • A text input field that must have a value before the form is submitted

  • A submit button

Both of these, along with the form, must be uniquely identifiable as well.

Here's how that will look:

<form id="nusmods-form">
    <input id="course-code" placeholder="Course code (ex: MA1301)" required>
    <button type="submit">Find course</button>
</form>

Note that we do not define an action or method attribute because we do not want the form to actually submit, we just want it to trigger a fetch request.

Step 2 - Ready a container

Once the request completes, we'll need to put the course details somewhere, so it's a good idea to have a container element ready to accomodate the data. You could also directly place the details in the body, but a container helps to structure the page better. The container should not initially be visible to the user.

<div id="course-container" hidden></div>

(Optional) You may also want to have a separate element ready to show an error message to the user, unless you intend to display an alert instead.

Step 3 - Add an event listener to the form

Now we need to add a listener to the form that will wait till it is submitted. Then it should trigger a fetch request.

document.addEventListener("DOMContentLoaded", function() {
    document.getElementById("nusmods-form").addEventListener(
        "submit",
        () => {
            event.preventDefault(); // prevent the form from submitting to the default path "/" using this line
            // note: event is deprecated, but still works
            // try instead to look at form validation functions
            // or try using this: arguments[0].preventDefault();
            getCourseData(); // this function will have the fetch request
        }
    )
})

When a form is submitted, it automatically submits data to its action URL. In this case since there is no action URL defined, the form attempts to submit the input to the page itself, which reloads the page and prevents the rest of the code from being executed. To prevent this, we have the line event.preventDefault() in the listener function.

event is a deprecated global variable that refers to the event in question. It is better to use form validation functions to prevent forms from submitting. In this case I am using event because it is easier, and form validation is out of the scope of this guide.

Step 3.5 - Prepping the data for the fetch request

In our getCourseData function from above (or whatever you decided to name the function), we need to ready the course code. This is nothing much, just get the input field value and make it all caps:

function getCourseData() {
    let inputField = document.getElementById("course-code");
    let courseCode = inputField.value.toUpperCase();
    // fetch request here
}

Note that the value attribute of an input field returns the value of the field, which in this case is whatever the user has typed into the text field.

Step 4 - The fetch request

Now comes the fetch request. Recall that to get course details based on course code, we submit a request to this URL:

https://api.nusmods.com/v2/2023-2024/modules/{courseCode}.json

So the resource parameter for the fetch request will be the above URL with the user's input plugged in. As for the options parameter, luckily we do not need to specify any because the default options suffice!

So the fetch request looks like this:

fetch(`https://api.nusmods.com/v2/2023-2024/modules/${courseCode}.json`, {});

Next, we need to check the response status. The API will return 200 if the course code was valid, 404 if it wasn't, and other codes indicate other unforeseen errors. You can decide how you want to process it, but in this case since we don't care what the response code is, we can return null if there is an error.

fetch(`https://api.nusmods.com/v2/2023-2024/modules/${courseCode}.json`, {})
.then(response => response.status === 200 ? response.json() : null);

Lastly, we need to process the data and present it to the user. For starters, try displaying the course code, course title, description, and how many units it is.

Here's the basic skeleton:

fetch(`https://api.nusmods.com/v2/2023-2024/modules/${courseCode}.json`, {})
.then(response => response.status === 200 ? response.json() : null)
.then(data => {
    if (data === null) {
        // course code was invalid, display an error
    } else {
        // data contains the course details in a JSON format
        // read the documentation to see what properties are relevant to your needs
        // OR copy paste the example URL from the start of this section into your browser
        // to see the JSON for yourself
    }
});

That's the fetch request completed, now in the body of then we need to display the course details

Step 5 - Display the course details

There are 3 main ways to do this, and it's up to you to choose which one. The implementation is left to you as an exercise.

Approach 1 - The builder method

This approach involves creating elements and adding the text inside of them, then appending them inside the course-container element we made earlier. Make use of the document.createElement method, the element.appendChild method and (optionally) the document.createTextNode method.

The advantage of this is that it is quite flexible to changes in specification. For instance, if I decide to also show the prerequisites and corequisites of every course, its easier to just edit the JS file to create a couple new elements.

The disadvantages of this approach is that every time the fetch request is run, the elements are re-created and re-added to the page.

This approach is fine for instances where requests are not very often (or happen only once or at most twice after the page loads), but in our case it is slower than the other approaches.

Approach 2 - The select-and-change method

For this approach, you'll need to edit the html to add elements for the course details to be contained within, and give them all ids. An example is below:

<div id="course-container" hidden>
    <h1 id="cc"></h1>
    <h2 id="course-title"></h2>
    <p id="course-description"></p>
    <p id="credits"></p>
</div>

You'll then need to query for these elements and edit their innerText properties to contain the data needed.

The advantage of this approach is that the elements are only created once, and you just need to change their values.

The disadvantage of this approach is that the code is not that flexible to changes in specification, since you need to edit both the HTML AND the JS script to implement any changes.

This approach is useful for instances where multiple fetch requests could be performed by the user, like this one.

Approach 3 - The lazy method

This approach is the quickest way to do it. Remember the innerHTML property? It holds everything inside the element in question, including nested elements. This means that it is possible to add elements inside of another one by way of the innerHTML attribute:

let element = document.querySelector("div");
element.innerHTML = "<h1>Heading</h1>";

The above code will insert an <h1> into the div that was selected.

This means we can just format the entire contents of the course-container div into a string, and set the element's innerHTML to that string.

The advantage of this approach is that it is fast and easy to write, and it is easy to implement changes to the specification.

The disadvantage is that this approach allows for something called HTML Injection. This is when malicious code can be injected into a webpage, and one of the ways to do this is to use the innerHTML property. Look at the code below:

let element = document.querySelector("div");
element.innerHTML = "<script>alert('HTML Injection Successful 😈');</script>";

This adds a <script> tag inside the div, which causes the code inside it to be executed. Here, the code is just showing a simple alert, but it is possible to write code which behaves much more maliciously, such as creating and submitting invisible forms, reading cookies set by the page, messing with the page content, or worse.

This method, due to the vulnerability it creates, is not recommended for use at all, except in cases where the developer has complete control over the content being added to the innerHTML or it is extremely certain that the content being read in is safe, and when the contents are not very long.

The end result

The code for this exercise can be found here. Approach 1 has been used because we have not yet done a real example of creating+adding elements. Approach 2 has been left as an exercise, and approach 3 has been demonstrated just to show how it works.

Next steps

This is the end of the JavaScript guide! If you would like to practice more fetch requests, I suggest testing out some of the API endpoints of Reqres. There is also an example here that shows a couple of the endpoints, and a two fetch request examples (one of them using a PUT request, so take a look at the options JSON for that request).

The next guide will be on React, a framework of JavaScript that allows to combine HTML, CSS and JS into a single abstraction to make frontend development slightly easier.

Last updated