As I tell my students very often, there is no Goldilocks solution. I have previously written about my use of cgi-bin to serve grades to my students when the official website was not available for some reason.

I wrote it using Python and cgi-bin because that was the quickest way to do it at the time with very little additional software required. Now that I have more time, I have decided to port the website to Node.js.

There are a number of design choices necessary, mainly what application framework to use and what templating engine to use. I decided to use express and EJS for these two aspect respectively. They are just easier for me to use than their alternatives. So lets get down to the code.

The actual code

The first thing I do is import express, body-parser, xlsx, and path. I need express as that is the framework I chose for developing my app — it abstracts a ton of work that I would otherwise have to do myself to manage the server. By using express, I outsource all the boilerplate code to that engine, and concentrate on writing the application logic instead.

I use body-parser to parse the output of the form sent to the server. This is done by attaching it as a middleware in a catch-all route. The fourth line of code in the block of code below is where I do this. I choose urlencoded as that is the default encoding scheme for html forms. The extended: true option is to allow parsing of rich elements, instead of text only. I also include xlsx to read the excel file.

The last include is path, I use this module to create file paths that work on any operating system — to avoid the idiosyncrasies of path naming in Windows vs Linux vs MacOS etc. The last bit of code in the first snippet is to register a callback function on the /mygrades route. I use the EJS templating engine, so the function merely calls render with the EJS template file for asking the student to enter his/her ID. I also inject pageTitle into the template, where it is used to display the appropriate page title.

const express = require('express');
const bodyParser = require('body-parser');
const XLSX = require('xlsx');
const path = require('path');


app.use(bodyParser.urlencoded({ extended: true }));

app.get('/mygrades',(req,res,next) =>{
  res.render('mygrades.ejs',{pageTitle: "My Grades"});
})

Next, I define a function called getMyGrades. This function takes three parameters, the student id, the data read from the excel file of student grades on the server, and a response object that can be used to send a response to the browser making the request.

As you can see, I define a string, called result, to which I concatenate all the grades of a student if his/her ID is found in the data read from the excel file. Otherwise, I set result to “This ID was not in the grades file.”. In both cases I encapsulate the string in <p> and </p> as I plan to inject this string into a template and have it rendered as html. This is what the last line of the function does, it calls render on the res object, with the appropriate EJS template, result and the pageTitle.

function getMyGrades(studentId,data,res){
  let result='';
  let found = false;
  for (let row of data){
    if (Number(row[0]==Number(studentId))){
      found=true;
      result+='<p>';
      for (let grade of row){
        result+=grade+' ';
      }
      result+='</p>'
    }
  }
  if (!found){
    result+="<p>This ID was not in the grades file.</p>";
  }
  res.render('showme.ejs',{result, pageTitle: "Your grades are"});
}

Of course, this function needs to be called from a route handler. The code below sets this up. First, I get the parsed form output and place it in dataFrame — I will use this in the last line of code to pass the student ID to the function above. Next I read the excel file and extract the appropriate worksheet. I then convert the sheet data to JSON. Finally, I send the ID, JSON data read from the excel file, and the res object to the function above.

app.post('/mygradesare',(req, res) => {
  const dataFrame = req.body;
  let workbook = XLSX.read('grades.xlsx', { type: 'file' });
  let sheetName = workbook.SheetNames[0];
  let worksheet = workbook.Sheets[sheetName];
  let data = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
  getMyGrades(dataFrame.studentId,data,res);
});

Below are the EJS templates I used to render the user interface. The only interesting parts are the <%- include(…) %> statements, I use these for two different purposes. The first is to include html and CSS code for styling, branding and analytics. This code is in partials/start.ejs and partials/end.js. The second <%- result %> is to inject the grades generated by the getMyGrades function into the EJS template for display.

<%- include('partials/start.ejs') %>
    <section class="owner-section"
    <h2>Your grades are:</h2>
    <%- result %>
    </section>
</body>
</html>
<%- include('partials/start.ejs') %>
    <section class="owner-section">
      <h2>Student Grades</h2>
 		<form method="POST" action="/mygradesare">
    			<label for="studentId">Student ID:</label>
    			<input type="text" id="studentId" name="studentId" required>
    			<button type="submit">Retrieve Grades</button>
  		</form>
    </section>
  <%- include('partials/end.ejs') %>

That’s it ladies and gents, this is how I converted the cgi-bin application to node.js. You can view the complete website here, and the GitHub repository for the site is here.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.