Project Work

This page contains instructions for the project work.

Introduction

As project work you should (individually) create a website one user can login to and administrate. We recommend you to create a personal website you can login to and write information about yourself. Having such a website can be very beneficial for a programmer applying for a job. If you prefer you can create a website for a small company with a single login account that can be used to update what products the company have in store, or similar.

You have a lot of freedom when it comes to the design and functionality of the website, but the following three requirements exist:

  • There should exist basic pages with trivial information, including:
    • A home/start page
    • An about page
    • A contact page
  • There should exist at least three different type of resources on the website (stored in a relational database)
  • Depending on what makes most sense, only you or anyone should be able to apply CRUD operations on the resources through the website

To be able to distinguish only you from anyone, you also need to have login-functionality for a single account (i.e. a hard-coded username & password).

Use whichever type of resources you want. Some suggestions are:

  • Blogposts (only you can create/update/delete entries, anyone can retrieve entries)
  • Software projects for a portfolio (only you can create/update/delete entries, anyone can retrieve entries)
  • Guestbook posts (only you can update/delete entries, anyone can retrieve/create entries)
  • FAQ entries (only you can update/delete entries, anyone can retrieve/create entries)

Implement the website using technologies taught in this course. It is OK to use similar technologies and not the specific ones taught in the course. For example, instead of using the web framework Express (taught in this course), you can use the web framework Koaopen in new window. It's probably a good idea to email the course coordinator and ask if the technologies you want to use is OK or not if you don't stick to using the ones taught in the course.

Don't use client-side JavaScript!

Avoid using client-side JavaScript. If you insist on using client-side JavaScript, it is OK, but:

  • Good client-side JavaScript code will not improve your grade (this course is not about learning client-side JavaScript)
  • Bad client-side JavaScript code will lower your grade (this course is about implementing good websites)
  • Your entire website must work for users who have JavaScript disabled in their web browser (use client-side JavaScript only to enhance the browsing experience for the users who have JavaScript enabled in their web browsers)

In addition to implementing the website, you should also write a report describing the implementation of the website. The file project-report-template.docx contains a template with further instructions on this. Your report will be a living document throughout the course (meaning that you will write it as the course run and continuously improve it).

The page Project Grading Guidelines contains guidelines for how your work will be graded. You are strongly recommended to read through it every now and then.

To help you with your project work, we have divided it into smaller parts that should be completed in order. The rest of this page contains descriptions of these smaller parts. Before you start working on the first part, you are strongly recommended to read through all the parts, which will give you a good overview of the work that lies ahead.

Good luck!

Installing required software

If you work on the computers in E2404, E2432 or E2433, all required software should already be installed for you, so no need to install anything on your own.

If you work on a school computer in any other room, you'll most likely need to manually install the required software through the Software Center application.

If you work on your own private computer, you'll need to download and install all required software yourself. In this course, we only use free software available for both Windows, Mac and Linux.

The required software is:

  • Node.jsopen in new window
    1. Use the LTS version
    2. In the installation process, make sure the following features are installed (they are checked by default, so simply don't uncheck them!):
      • Node.js runtime
      • npm package manager
      • Add to PATH
    3. To verify that Node.js has been successfully installed, run the command node -v in a shell (e.g. Windows PowerShell on Windows (you might need to restart your computer first)). If it has been installed successfully, you should see the version of Node.js that was installed, something like v11.6.0
  • npmopen in new window
    • This one is installed along with Node.js, so you don't need to install it separately. However, you might want to update it to the latest version (not required in this course):
    • To verify that npm has been successfully installed, run the command npm -v in a shell (you might need to restart your computer first). If it has been installed successfully, you should see the version of npm that is installed, something like 6.9.0

The following software tools are not required (use whichever tools you want), but recommended:

Part 1: Specifying the functionality

Before you start working on this part, you are recommended to:


Your first task is to specify the functionality of the website (which different type of resources it consists of and what you/anyone can do with them). Your decisions should be described in the project report, so in this part of the project work, you will not do any programming, but only work on your report. A good way to describe the functionalities is by sketching what the GUI will look like in the end. Then the reader can easily see all the functionality just by looking at pictures. An example of that is shown in project-report-template.docx.

Feel free to show your report to one of the teachers at the lab sessions to get some feedback on your work. Don't expect the teacher to read all of your report, but primarily to look at the figures in it, but that should be enough to give you valuable feedback.

Part 2: Specifying the graphical user interface

Before you start working on this part, you are recommended to:


Designing a website with a user-friendly GUI is no simple task. Therefore, designers have created CSS frameworks that makes it easy for people who are not good at design to create GUIs that are user-friendly. These frameworks primarily consist of components one can use to implement the GUI (such as one component representing a form, another one representing a menu, a third one representing an article, etc.). Since you have to use a CSS framework in your project work, you are recommended to now select which CSS framework you are going to use, and then design your website using the available components in that framework.

Use whichever CSS framework you want, but we recommend you to use Spectre.cssopen in new window. It is a small and simple framework, which makes it easy to learn and use. It comes with components such as:

  • Navbaropen in new window - A horizontal menu suitable to have at the top of the page as the main menu
  • Navopen in new window - A vertical menu suitable to have on one of the sides of the page as the main/sub menu
  • Paginationopen in new window - A collection of links suitable to use when listing resources spread over multiple pages
  • Empty statesopen in new window - A component suitable to use on a page that should display a collection of resources but none exist yet
  • Modalsopen in new window - A dialog appearing in front of the page
    • Note: You can make modals appear without using client-side JavaScript. When clicking on a "Show modal" button (a link), send back HTML code containing and showing the modal
  • Tabsopen in new window - A collection of views the user easily can switch between

We recommend you to use a simple layout. It is more important that the functionality on the website works than that it has a beautiful design. If you have time over at the end of the course you can try to make your website as beautiful as possible, but for now we recommend you to prioritize making it functional.

Remember...

The user must be able to use your website even if the user disable client-side JS in her web browser, so don't use an HTML and CSS framework that also contains client-side JS code the framework is dependent on to function!

In the report, draw sketches (e.g. wire-frames) showing what you intend the GUI to look like. In the end, you don't have to implement the GUI as you specify it now in the report, but remember to update your report if you change it later. Implementing the GUI is next part of the Project Work.

Feel free to show these sketches to a teacher at a lab session to get some feedback.

Part 3: Starting to implement the web application

Before you start working on this part, you are recommended to:


Implement the graphical user interface of the website in Node.js. Ideally, the website's recourses are stored in a database, but to get a gentler start, you can instead store the resources in a file (i.e. use "dummy data") for now. To start, run the following commands in a shell/console/terminal (such as Windows PowerShell in Windows):

  1. mkdir my-website - Creates a new folder named my-website (do not call the folder my-website; use a proper name)
  2. cd my-website - Changes the current working directory to my-website
  3. npm init --yes - Creates the package.json file, which keep tracks of which npm packages you have installed
  4. npm install express - Installs the npm package express
  5. npm install express-handlebars - Installs the npm package express-handlebars

Tips

When you version control a project, do not store the source code for the dependencies in your own repository. For Node.js applications, the node_modules folder should not be committed to your repository; it's enough to commit package.json and package-lock.json to your repository. These files contain a list of all your dependencies (and the specific versions of them), and others that clones your project can run the command npm install to install these dependencies in their own node_modules folder.

Note

The instructions in this document is for the template syntax language Handlebars. If you prefer, feel free to use another template syntax language. You find a list of template engines you can use at https://github.com/expressjs/express/wikiopen in new window.

Then create the following files with their corresponding content:

exports.humans = [{
  id: 0,
  name: "Alice"
}, {
  id: 1,
  name: "Bob"
}]

exports.pets = [{
  id: 0,
  humanId: 1,
  name: "Catty"
}]
const dummyData = require('./dummy-data')
const express = require('express')
const expressHandlebars = require('express-handlebars')

const app = express()

app.engine("hbs", expressHandlebars.engine({
  defaultLayout: 'main.hbs'
}))

app.get('/', function(request, response){
  const model = {
    humans: dummyData.humans
  }
  response.render("show-all-humans.hbs", model)
})

app.listen(8080)
<h2>All Humans</h2>
{{#if humans.length}}
  <ul>
    {{#each humans}}
      <li>{{name}}</li>
    {{/each}}
  </ul>
{{else}}
  <p>No humans exist.</p>
{{/if}}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello</title>
  </head>
  <body>
    <h1>My Website</h1>
    {{{body}}}
  </body>
</html>

Then run the following command:

  1. node app.js - Execute the code in the file app.js using Node.js.

and then visit http://localhost:8080/open in new window in a web browser, and you should see a list of humans.

Continue by changing and adding code so it instead looks like your own personal website, as you specified in your report. Here is a suggestion on in which order to implement the features:

  1. Add references to your CSS framework in the layout file
    • For many CSS frameworks, you can just add a <link> element to the CSS file containing the framework's code on a Content Delivery Network (CDN)
    • Many CSS frameworks are distributed as npm packages. For these, you can download the source code for the framework using the npm install *** command, and then add a <link> element to the CSS file in the node_modules folder
  2. Implement most of the layout of your website by changing the code in views/layouts/main.hbs
  3. Create new views (new .hbs files in the views folder) representing your basic pages (about page, contact page, etc.). Then change your JavaScript code so web browsers can request these pages

Remember!

Each time you change your JavaScript code, you need to restart the application for the changes to take effect. In most shells, you can stop the application from running by pressing [CTRL] + [C] twice, and then to start it again, simply run the command node app.js again. In most shells, you can press the up arrow on the keyboard to get the previously executed command.

  1. Change your dummy data to represent your own resources instead. Your dummy data will later be replaced with a relational database, so try to structure the resources the same way as you would structure them in a relational database (this will make the transition to a relational database easier).
  2. Add new views representing pages with CRUD operations for your resources. Then change your JavaScript code so web browsers can request these pages.

Note!

You will use forms in your views (for create, update and delete operations), but handling the submission of these forms will be dealt with later. Implementing the login functionality is also something you will do later. For now, just having these forms is enough; nothing should happen when they are submitted.

When you are done, it should be possible for a user to visit all the pages on your website, including the ones only the logged in user should be able to access (will be fixed later). Update your report with a description of your implementation so far.

Feel free to show your work so far to a teacher at a lab session to get some feedback on it.

Part 4: Using a relational database

Before you start working on this part, you are recommended to:


Storing data in variables works, but it is usually a bad solution for several reasons:

  • When the web application stops running, the data will be lost
  • Computers' main memory (where variables are stored) is quite limited, so storing a big amount of data here is not appropriate
  • It makes it hard to scale the web application (having it running on multiple servers at the same time), which is needed when your website becomes really popular and have a lot of visitors (will happen sooner or later, right? 😉)

Most web applications instead store data in a relational database, such as MySQL. Relational databases store the data in secondary storage (i.e. the hard drive), where it will persist even after the computer is shut down. Usually, the database runs on a server separate from the server the web application runs on, as shown in below.

Common web application architecture.

Having the database running on a separate server from the web application (instead of on the same server) has a couple of advantages:

  • Web applications and databases have different hardware needs. Databases read/write a lot from/to secondary storage, so they need to do this fast to operate efficiently. But good/fast secondary storage (i.e. SDDs) is expensive. Web applications on the other hand process a lot of data (incoming HTTP requests), and instead need fast (expensive) CPUs, and possibly much primary storage to utilize caching. By using two different servers, you can better match the software with its hardware needs
  • If the web application and the database run on the same server, the maximum number of HTTP requests the web application can handle will be much lower, since the web application only have access to the hardware "half of the time" (the hardware is shared with the database)

However, setting up a database on a separate server is not straightforward and takes time, so in this course we will instead run the database on the same server as the web application, as shown in below.

Web application architecture with only one server.

Furthermore, we will use SQLite as our database, and it will not run as a separate process, but part of the process executing our web application. This makes it very easy to use, but will be a bottleneck for scaling (but remember, premature optimization is the root of all evilopen in new window).

Start by installing an npm package through which you can use SQLite (you don't need to install SQLite separately, just installing the npm package will be enough). You can use the package sqlite3 for this. Then delete dummy-data.js, and create, store and retrieve your data from an SQLite database instead (putting this code in a file called database.js (or similar) makes sense). Make sure your tables use proper primary keys, foreign key constraints, unique constraints, etc.

Note!

Do not send CREATE TABLE xxx (...) queries to the database, because the second time your web application starts, the tables will already exist, and you will get an error. Use CREATE TABLE IF NOT EXISTS xxx (...) instead. And if you screw up, you can always start over by deleting the database file.

You can use DB Browser for SQLite to view your database and manually insert some resources.

Tips

The database is not part of the source code for a web application and should therefor not be committed to a version control repository. When someone else clones your project and start your web application, the database should automatically be created if it does not exist.

When you are done, your web application should work precisely the same way as when you had completed Part 3 but the resources should now be stored in the database instead of variables. Then update your report with a description of your implementation so far, and if you want feedback on your report and your code, show them to a teacher at one of the lab sessions.

Part 5: Handling forms

Deadline

Just a reminder that you should not forget to submit your report for feedback on Canvasopen in new window. The deadline might have already been (depending on how slow/fast you work), but if you follow the time plan, you should submit it around now.

When a user submits an HTML form (<form>), the web browser will send a new HTTP request to the server. If the GET method is used (<form method="get">), the data entered in the form will be added to the query string in the URI (the part after the ? in the URI). In an express callback function receiving the request, that data can be accessed using the query property, i.e. request.query. This will be an object whose keys corresponds to the value given to the name attribute in the <input> fields in the form and the values will be the text the user entered in those <input> fields. It works similar for the other input elements you can use in forms (dropdown lists, checkboxes, radio buttons, etc.).

Example

<form method="get" action="/search">
  City: <input name="city"><br>
        <input type="submit">
</form>
const express = require('express')

const app = express()

// GET /search?city=WHATEVER_THE_USER_ENTERED
app.get('/search', function(request, response){
  const city = request.query.city // WHATEVER_THE_USER_ENTERED
  // ...
})

app.listen(8080)

If the POST method is used to submit the form (<form method="post">), the data in the form will be added to the body of the request, expressed in the data format application/x-www-form-urlencoded (the header Content-Type: application/x-www-form-urlencoded is added to the HTTP request). An Express app does by default not contain functionality to read this data, but the middleware express.urlencoded() can be used to add it. Once you have added it, you can access the data entered in the form using request.body.

Updated instructions

Before it said you needed to add bodyParser.urlencoded(). These days it's part of the express package, so these days one can use express.urlencoded() instead. The instructions have been updated to reflect this.

Example

<form method="post" action="/search">
  City: <input name="city"><br>
        <input type="submit">
</form>
const express = require('express')

const app = express()
app.use(express.urlencoded({
  extended: false
}))

// POST /search
// Body: city=WHATEVER_THE_USER_ENTERED
app.post('/search', function(req, res){
  const city = req.body.city // WHATEVER_THE_USER_ENTERED
  // ...
})

app.listen(8080)

Change your JavaScript code to handle submissions of forms (except the login and logout forms). When you are done, any user should be able to create/edit/delete resources through the forms. Then update your report with a description of your implementation so far.

GET vs POST

When should you submit a form with the GET method, and when should you use the POST method? Remember that GET requests should only be used to retrieve resources, and they should not change anything on the server (no harm in sending the same request 10 times in a row by accident), while POST requests may change things on the server or have some other type of side effect, such as sending an email.

Show your work to a teacher at one of the lab sessions to get some feedback on it.

Part 6: Handling errors

When a user submits a form to create/edit a resource, you need to validate the data entered in the form, and only carry out the request if the entered data is valid. For example, the user might have forgot to enter data in a form field, or maybe the user has entered some invalid data (for example entered -43 as the number of pages for a book resource). If the data is not valid, you should display the form to the user again (with the data the user entered), together with descriptive error messages explaining what was wrong.

Your route parameters (such as bookId in /books/:booksId) needs to be validated too. Maybe the user visits your website and ends up at /books/4, bookmarks this page, and then you delete it from your database. If the user now goes to this page through her bookmark, she should not see an empty/crashed page, but a message saying that the book does not exist.

It is not only data that comes from your clients that can result in errors. External systems your web application depends on (such as the database) can fail as well. For example, if the database is full, you cannot insert a new resource into it. In these cases, there is no need to show a descriptive error message like The database is full, because the user cannot fix the problem anyway. Instead, it is better to show a general error message like Server error, we are working on fixing it, and then log the error details, so you then can debug and fix the error.

Deal with the various errors that can happen in your web application, and then update your report with a description of your implementation so far.

Part 7: Adding authentication & authorization

Before you start working on this part, you are recommended to:


To distinguish you from anyone (which is needed for operations only you should be authorized to carry out), you need to be able to authenticate yourself (prove that you are you). This can be done by hard-coding a username and a password on the server only you know. When you later login, you send this username and password to the server, which compares them with the username and password that have been hard-coded, and if they match, the server can trust that the request comes from you, because you are the only one who should know the correct username and password. Simple as that!

However, HTTP is a stateless protocol. This means that the server does not need to remember HTTP requests it has previously received to carry out HTTP requests it will receive in the future. This makes it a bit complex to stay logged in, because the server does not remember that you have previously successfully logged in when you send it other requests after that.

For servers to remember information about clients, cookies were added to HTTP. A cookie is a key-value pair the server can send to the client in an HTTP response, and the client should then send this cookie back to the server in future HTTP requests it sends to the server. However, using a cookie like isLoggedIn=true is not a secure way to remember that a client has successfully logged in, because there is nothing preventing a hacker from manually creating an identical cookie on her own computer with the same information.

To securely remember information about clients, you can use sessions. Session are built on top of cookies, and consists of key-value pairs stored on the server. Each client gets its own session, and each session has its own unique and hard-to-guess id, known as the session id. The server sends the session id to the client in a cookie when the session for that client is created. The client then sends back the cookie with the session id to the server in its future HTTP requests, and the server can then lookup the information it has stored about that client in the session with the session id it receives in the cookie.

Use sessions (and cookies) to implement a secure login system for a single account. To use session in an express application, you can use the npm package express-session, and add it as a middleware function.

Example

Example of how to use the express-session package in an Express application.

const express = require('express')
const session = require('express-session')

const app = express()
app.use(session())

// Use sessions to count how many times each client has visited this page.
app.get('/the-page', function(request, response){
  if(!request.session.counter){
    request.session.counter = 0
  }
  request.session.counter += 1
  // ...
})

app.listen(8080)

Store sessions properly!

The example above stores the sessions in main memory, and it never removes old sessions. If such a solution would run on a real server, it would run out of memory quite soon. Instead, read the docs for express-sessionopen in new window to learn how to store the sessions elsewhere.

Part 8: Improving security

Before you start working on this part, you are recommended to:


SQL injections

If you have code like the one shown in below, you are vulnerable to SQL injections.

const express = require('express')
const sqlite3 = require('sqlite3')

const app = express()
const db = new sqlite3.Database('db.db')

// Send back a book with a specific id.
app.get('/books/:id', function(request, response){
  const id = request.params.id
  const query = "SELECT * FROM books WHERE id = "+id+" LIMIT 1"
  db.get(query, function(error, book){
    if(error){
      // Send back page showing an error message.
    }else{
      // Send back page showing the book.
    }
  })
})

app.listen(8080)
Example of code vulnerable to SQL injections.

Most of your users will go to URLs like /books/53, and the query sent to the database will be like SELECT * FROM books WHERE id = 53 LIMIT 1, as expected. But a hacker might try to visit the URL /books/1 OR title='hello', and the query sent to the database will be SELECT * FROM books WHERE id = 1 OR title='hello' LIMIT 1. Although no harm has been done in this case, it shows how hackers can manipulate queries sent to the database, and potentially read (or delete, update, insert, etc.) information they should not be able to.

Data received from users, no matter if it comes from the URL, the query string, cookies or the body of the request, cannot be trusted, so manually constructing SQL queries through string concatenation as done in the code above is very dangerous and must be avoided. Instead, you can use placeholders, as shown in the code in below.

// Send back a book with a specific id.
app.get('/books/:id', function(request, response){
  
  const id = request.params.id
  
  // Use ? as placeholder for values we retrieve from the clients.
  const query = "SELECT * FROM books WHERE id = ? LIMIT 1"
  
  // The values we retrieved from the client (will be used instead of the placeholders).
  const values = [id]
  
  db.get(query, values, function(error, book){
    if(error){
      // Send back page showing an error message.
    }else{
      // Send back page showing the book.
    }
  })
})
Example of code not vulnerable to SQL injections.

This way, the query with the placeholder is sent to the database as one unit, and the value for the placeholder is sent as another unit, so the data cannot alter the query sent to the database, and you are protected against SQL injections.

Make sure all your code is protected against SQL injections.

Hashing passwords

Passwords should never be stored in plain text. Humans often use the same password on different websites, and if a hacker comes across a user's password on one website, she can login as that user on the other websites that user is using. Quite bad!

Instead of storing passwords in plain text, one can hash passwords and store their hash values. These hash values cannot easily nor with 100% accuracy be reversed back to the original passwords again.

There exist hashing algorithms specifically designed to hash passwords (they are intentionally slow). Use one of them to securely store your account's password. You can for example use the npm package bcrypt to accomplish this.

bcrypt uses Python

The npm package bcrypt might need a Python installation on your computer to work. If you can't get it to work, you can use bcryptjs instead, but that one is slower since it's implemented in pure JavaScript.

When you are done, you should have a hard-coded hash value of your password in your source code instead of a hard-coded password in plain text. To obtain the hash value of the password you want to use:

  1. Install bcryptopen in new window
  2. Create a new temporary JS file
  3. In that file, import the bcrypt package
  4. Use a function from the bcrypt package (for example hashSync() (read the documentation for the npm package to see example of usage)) to compute the hash value of the password you want to use
  5. Log the hash value
  6. Use the node program to run the file and obtain the hash value
  7. Store the hash value in your app.js file (instead of the admin password)
  8. When you receive the login request from the user, use a function from bcrypt (for example compareSync() (read the documentation for the npm package to see example of usage)) to check if the received password is the correct one
  9. Be sure to not store the correct password in plain text anywhere in your source code (you can for example delete the temporary JS file you created after you have obtained the hash value of your password)

Cross-Site Scripting

The danger with SQL injections is that data coming from one user is injected and modifies the query sent to the database. Cross-Site Scripting (XSS) is something similar, but it modifies the HTML code sent to other users. For example, a hacker might write a new guestbook message and send it to your web application, and when other users read your guestbook, they receive the hacker's guestbook message. If the guestbook message contains HTML code, it will be sent to the users' web browsers and displayed as part of the website. With just HTML code, a hacker cannot do that dangerous things, but the HTML code can in turn contain client-side JavaScript code which can do much more damage when it is executed in the users' web browsers.

Make sure all data you receive from your users is escaped before it is sent to your users.

Note!

Most template engines automatically escape HTML code unless you use a special syntax. If you use Handlebars as your template engine, you can read more about it under [the guide at handlebarsjs.com](https://handlebarsjs.com/guide/#html-escaping). In short, if you use {{...}} instead of {{{...}}}, you have no XSS vulnerabilities.

Cross-Site Request Forgery

Not this year!

csurfopen in new window, Express' official npm package to add protection against CSRF attacks, was deprecated Sep 13 2022. Therefore we skip this part of the project this year. Those who still want to practice on adding protection against CSRF attacks can still do that using that very package (it should still work equally good as before, but one shouldn't use deprecated packages (they won't be maintained in the future), so I can't recommend using it), or you can use the npm package simple-csrfopen in new window (that package is not an official package from Express, and the author of the package is no longer that active on GitHub, and very few uses that package, so at the moment I can't really recommend using it either).

The instructions in this sub-chapter is left as they are for those who want to learn how to add protection against CSRF attacks.

Cross-Site Request Forgery (CSRF) is a type of an attack where hackers successfully trick our users into sending HTTP request to our website which they did not intent to send. For example, if our website contains a guestbook and is vulnerable to CSRF-attacks, a hacker might trick a user to send 1000 "create new guestbook message" requests. Our website just sees the requests coming from the user, and not from the hacker, so we think the user is the bad guy spamming our guestbook, but she is just the tool.

A spammed guestbook is no danger and can easily be fixed, but CSRF-attacks in general can be quite dangerous. Imagine you sign in on your bank's website, then you visit another website controlled by the hacker (the hacker does not need to own this other website; it is enough for the website to contain a XSS-vulnerability the hacker can exploit to inject bad client-side JavaScript code into it) and tricks your web browser to send HTTP requests to your bank's website. One request can for example be to transfer money from your account to the hackers account. The bank's website thinks the request intentionally is sent by you and carries it out. Quite bad!

The funny thing about these CSRF-attacks is that the vulnerability is not on our website. It is when our users visit other websites they might become victim of CSRF-attacks that makes them unintentionally send HTTP requests to our website. Luckily, there is a way for us to protect us from these unintentionally sent request known as the Synchronizer Token Patternopen in new window.

The synchronizer token pattern takes advantage of the fact that if a user wants to send a request to our website, it must do so by submitting a <form> with method="post" on our website. Remember, in HTML, it is only possible to send GET request and POST requests, and only POST requests should potentially result in changes on the website, so only POST requests are dangerous in CSRF-attacks (CSRF-attacks are not used by hackers to retrieve sensitive data on the server). So, when our website receives a POST request from a user, we need to check if it comes from the submission of a form on our website, because it is only in that case the request has been intentionally sent by the user.

HTML/HTTP does not come with built-in support to check that, but the synchronizer token pattern describes a way for us to do it manually. It basically works like this:

  1. When a user sends a GET requests for the form, we generate a unique token (a long random string), remember this token (in the session, in a cookie, or whatever) and add it as a hidden input field to the form, e.g. <input type="hidden" name="token" value="the-long-random-string">, so we also receives it back when the form is submitted
  2. When we receive the submission of the form (i.e. the POST request), we check if the token in the form is the same token as we generated before. If they are, we can be sure the POST request has been intentionally sent by the user through the form on our website, and not through a CSRF-attack

Use the npm package csurf to protect your website from CSRF-attacks.

Part 9: Optional tasks

Here are some optional tasks you most likely must complete if you want to get a grade higher than 3. Remember that completing these extra tasks does not necessarily give you a higher grade; also read the Project Grading Guidelines.

Search or Pagination (required for grade 4)

Implement search or pagination for at least one of your resource types.

Add search functionality for at least one of your resource types. The user should be able to enter some search criteria in a form, and then the resources of that type that matches the search criteria should be shown. Some tips:

  • Searching is about retrieving resources, so the method in the form should be...?
  • When sending the query to the database you need to have a WHERE clause that takes the user's search criteria into account. You probably have much use of the LIKEopen in new window operator in SQL

You have a lot of freedom when it comes to this task, but avoid using too simply solutions, such as just searching for one field in the database. Maybe the user also can specify a date range or similar?

Pagination

Add pagination to at least one of your resource types, so not all of them are listed on the same page but spread out across multiple pages. Some tip:

  • Start by selecting how many resources that should be shown on each page
  • You can use a query string parameter to keep track of the page number, e.g. /movies?page=3
  • To count how many resources you have in a table, use the SQL function COUNT()open in new window (needed to compute how many pages you need)
  • When sending the query to the database to obtain the resources, use the LIMITopen in new window clause to only get back the resources on the page the user is on

1 resource per page does not count as pagination.

Uploading files (required for grade 5)

Make it possible to upload a file to at least one of your resource types. This could for example be a screenshot of a software application you have in your portfolio. How to do this is something you need to learn on your own (grade 5...).

Note that files should not be stored in a database (but on the file system), and does not count as one of the three type of resources you should have on your website.

You have a lot of freedom when it comes to this task, but try to at least properly handle errors.

Part 10: Deploying

Before you start working on this part, you are recommended to:


Deploy your web application on a server. Use whichever you want, but it needs to be publicly accessible for other computers on the Internet. It is fine to do it as shown in the video above (using a domain name and HTTPS is not required, but please remember that all websites should use HTTPS).

Heroku

One student in the course suggested that one could just as well use Heuroko (should also be free with the GitHub Student Packopen in new window) instead of Amazon Lightsail. Feel free to use whichever service you want to run your web app on the Internet. I'm guessing the page Getting Started on Heroku with Node.jsopen in new window covers what you need to know to deploy your web application in this course to Heuroko.

If you in Part 11: Demonstration demonstrates your website when it's online, the teacher will approve you on the Canvas assignment Project Serveropen in new window. Otherwise you need to have it up and running on a server when you complete Part 12: Submitting your work.

Part 11: Demonstration

Demonstrate how your application works to the rest of your class in smaller groups. The reason for the demonstration is two-folded:

  • You get some practice in presenting your work, which is a very important skill in your future professional career
  • You get to see other ways to implement similar functionality (hopefully not all websites will look the same)

At the demonstration you will simply show how users (you as the admin, and the rest of the world as visitors) can use the website. You should not show any code nor explain any implementation details; just demonstrate the website through a web browser. Show that you have CRUD operations for all of your resources, that you have validation, that you display descriptive error messages when something goes wrong, etc.

To be allowed to demonstrate your website, you need to have implemented at least:

  • A functional login system
  • All 4 CRUD operations for at least 2 type of resources with appropriate validation and authorization checks

You may use at most 10 minutes for your demonstration. If you need more than this to show all features, then skip some of them. If your demonstration takes just 2 minutes, that could be OK as well. Don't start talking about irrelevant things just to make the demonstration last longer.

Preferably bring your own laptop to the demonstration, and demonstrate your website using that one. If you can't do that, ask if you can borrow a laptop from a friend. If that's not possible neither, you can use the teacher's laptop for the demonstration, but then you have to have completed Part 10: Deploying first.

Join one of the Project Demonstration Groupsopen in new window on Canvas to decide when you want to demonstrate. At most 6 students in each group, first come, first served.

Your demonstration will not be graded; consider it as (mandatory) practice (you must do it to pass the project work).

Part 12: Submitting your work

When you are done with your project, submit it for grading by submitting the assignment Project Final Submissionopen in new window on Canvas. Upload:

  • Your project report as a PDF file
  • The source code of your website as a ZIP file:
    • Zip the root folder of your website (the project folder), and not a collection of individual files/sub-folder
    • Do not include the node_modules folder (e.g. delete it before you create the ZIP file)

Do not put the PDF file in the ZIP file; upload them as two separate files in one and the same submission.

When that ZIP file has been unzipped on the teacher's own computer, it is very important that the website can be started by running the following commands:

  1. npm install
  2. npm run start or node app.js (or whichever the name of your main file is)

If this does not work and your report doesn't contain any instructions on how to start your web application you need to submit your work again at the next examination occasion, so double check that this works yourself before you submit your work.

Write also the following information as a comment in the submission:

  • The URI to the login page
  • The correct username for logging in
  • The correct password for logging in
  • The IP address of the server your web application is running on (not required if you have already been approved on the assignment Project Serveropen in new window)

The examiner will login to your website and test all the CRUD operations.

The examiner will only look at your latest submission, so make sure that your latest submission includes both files (report + source code) and the comment with the requested information.

Your submitted work will be checked for plagiarism using Ouriginalopen in new window.

If not all requested files and information is submitted, we will not be able to grade your work.