Bookish

Learning goals

  • Using relational databses
  • Object-relational mappers (ORMs)
  • Linking different sources of data
  • The MVC model (Model-View-Controller)
  • Developing effective user interfaces

Programs Used

Overview

We’re going to build a simple library management system. We’d like it to support the following features:

  • Book Management
    • The librarian can browse the catalogue of books
      • Considerations: How are the books going to be sorted? By title? By author?
      • Could we add a search feature?
    • The librarian can edit the catalogue of books
      • Add new books
      • Update the details of a book
      • Add a copy of an existing book
      • Delete a copy of a book
    • For each book, we will need details of
      • The total number of copies of that book
      • The number of copies that are currently available
      • Which users have borrowed the other copies
  • Member Management
    • The librarian can see a list of library members
      • With a list of the books that member currently has checked out
    • The librarian can add a new member
    • The librarian can edit the details of an existing member
  • Checking books in / out
    • The librarian can check out a copy of a book to a user
    • The librarian can check a copy of a book back in
      • Notification for late returns?

Tip

There isn’t a lot of time to implement all of these features, so don’t worry if you don’t get all of them done. What’s important for this exercise is that you gain an understanding of how data moves through your system, and how to manipulate objects in your database.

Setup

Creating a repository

First you need to set up a GitHub repository for the project – simply follow the Create a repository section of this tutorial, name the repository something like Bookish.

Once you have done this, clone (download) the repo:

  • Go to your newly-created fork of the repo (on GitHub).
  • Click Code (the green button on the right).
  • Make sure you are on the SSH tab (rather than HTTPS or GitHub CLI).
  • Copy the link this displays (it should look something like this: git@github.com:account_name/repo_name.git).
  • Open your git client and use this link to clone the repo.
  • Your trainer will able to help you with this.

“Cloning the repo” will create a folder on your computer with the files from this repo. Open this folder in Visual Studio Code.

Creating a Web API with ASP.NET Core MVC

We will be using ASP.NET Core MVC to create a Web API for our Bookish app. It would be a good idea to familiarise yourself with ASP.NET Core MVC by reading through this overview, though you should note that we will only be using it to create Models and Controllers, not Views. Instead of serving Views, our Web API will serve JSON responses, so there is no need to concern yourself with any of the information on Views for now.

Start a new mvc web app by running the following command inside the folder which will contain the bookish application dotnet new mvc.

This should give us a nice template for a simple application. Check it runs!

Let’s make sure that we understand what’s going on here.

You should find that there are several folders within the application. bin, obj and Properties aren’t really important to us right now, and you can safely ignore them! The others are more interesting though!

Controllers

The home of all of the controllers for the application. Right now its just the HomeController, but you can add more as you need them. Each controller class should extend the ControllerBase class.

Routing

You can declare actions in these controllers. An action is really just a method that returns an ActionResult.

By default, each action will create an endpoint at a URL based on the name of the method and the name of the controller. For example, the Privacy action within the HomeController method can be found at the endpoint /Home/Privacy. (There is also the special name Index, which is treated as the default option, so the URL /Home will use the Index action within the HomeController)

If you don’t like this default behaviour and prefer something more explicit, then you can also specify the endpoint URLs yourself.

[Route("my-route")] can be applied to the controller class to override the default base URL for the controller.

[HttpGet(“my-action“)] can be applied to the action to override the default path (There are also similar attributes for Post, Put etc.).

Models

Much less to talk about here – models are nice and boring! This directory is your home for the models that you will need to populate your views with data.

Models are just classes containing any properties or methods that your view needs.

Views

(Shockingly) this is where the Views live. As mentioned above, we will not be concerning ourselves with them.

wwwroot

This is where our static content (things like css, js, images etc) lives. Everything in this directory is going to be served on the site at a URL determined by its file path. eg, the file at /wwwroot/css/site.css will be found at /<content-root>/css/site.css.

Bootstrap

The default app uses bootstrapfor the styling. This is probably fine for now, as we are unlikely to be doing much styling in this exercise.

Setting up PostgreSQL

If you haven’t got PostgreSQL installed already, you’ll need to download and install it from the PostgreSQL website. Leave all the options as the default, but untick “Launch Stack Builder at exit” on the last screen.

Warning

Make sure you remember the username/password you set when installing!

When you installed PostgreSQL Server, you may have also installed pgAdmin (if not, download and install it from here). This will allow you to manage your PostgreSQL server.

Navigate to Servers > PostgreSQL > Login/Group Roles (when you’re asked for a PostgreSQL password, put in the one you chose above for installing Postgres): right-click and create a new login/group role named bookish with a suitable password and privileges “Can login” and “Create databases”. 

Go to Servers > PostgreSQL > Databases, right-click and create a new database named bookish with owner bookish.

Make sure you can fire up PostgreSQL Server and connect to the database using pgAdmin. If you can’t, please speak to your trainer to help get it sorted.

Designing the database

You’ll need to come up with a database structure (i.e. what tables you want in your database. This is similar to deciding what classes to have in your application). Think through your database design. Work out what tables you need to support the scenarios outlined above. Discuss this with your trainers before you create things in the database.

You’re going to be using an ORM (object-relational mapping) to help manage the database. These help to abstract the interactions with the database by creating Models to represent the tables in your database, and Migrations to manage changes to the structure of your database. Depending on how you’ve decided to structure your database, it’s likely you’ll want to start by creating a Model for Books, and a Migration to manage adding the Book table to your database.

The ORM we are going to use is Entity Framework (docs found here).

Install it, and the PostgreSQL library, by running the following:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL

Code-First migrations

We’re going to be using code to generate the structure of our database, we can tell Entity Framework what our database should look like based off our class structure and it will take care of creating and updating our database to match the structure of our code!

First run dotnet tool install --global dotnet-ef to allow you to run dotnet entity framework commands in your terminal.

Read through and implement the tutorial for your first model(s) and migration(s) here substituting the Student and Course classes for the class(es) relevant to your database design. Do find your trainer if you want any help or guidance on this step!

A good first step after installing PostgreSQL and Entity Framework is to create a file called BookishContext.cs that looks something like this:

using Bookish.Models;
using Microsoft.EntityFrameworkCore;

namespace Bookish { 
    public class BookishContext : DbContext { 
        // Put all the tables you want in your database here 
        public DbSet<Book> Books { get; set; } 
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { 
            // This is the configuration used for connecting to the database 
            optionsBuilder.UseNpgsql(@"Server=localhost;Port=5432;Database=bookish;User Id=bookish;Password=<password>;"); 
        } 
    } 
}

When writing code to configure the connection to the database, the username/password should be the ones you set up previously (i.e. bookish / <password>) – at this stage we can put these values directly in our code, but remember this is bad practice; such values should be externalised and passwords should be encrypted.

Once you’ve done this, it would be a good idea to add some sample data to your database. You can do this through pgAdmin by right clicking on on the bookish database, selecting Query tool, and executing an INSERT statement.

Building an API

Let’s start building an API that your frontend can call to in order to interact with the database.

You’ll want to start by implementing a basic endpoint: GETing all the books available in the library.

First, create a new controller called BookController, this should be a class that extends ControllerBase (not Controller which provides support for Views). Mark this controller with the [ApiController] attribute to provide automatic HTTP 400 responses and enforce best practice. You should also use the [Route(<route>)] attribute to specify a URL pattern for the controller – "api/[controller]" would be a sensible choice of route. Note that [controller] will automatically be substituted by a value based on the name of your controller, so the route will be configured as api/book for the BookController. The controller will need a BookishContext instance variable so that it can query the database.

Within the BookController define a new method, or Action, this Action should be called Get and return an IActionResult. It should also be marked with the [HttpGet] attribute. This method should return a JSON-formatted list of all the Books in the BookishContext. Note that the built-in helper method ControllerBase.Ok returns JSON-formatted data and will produce the desired Status200Ok response.

To test your endpoint is working, simply run the app with dotnet run and use your browser or a tool like Postman to test your API endpoint and check that it’s all working as intended. Provided you followed the instructions above the endpoint url should be http://localhost:<port>/api/book. It should return a JSON-formatted list of all the books in your database.

If you’re struggling, you may find the ASP.NET Core Web API documentation to be helpful.

Once you’re satisfied that that’s working, have a go at implementing a few more of the features in your API, starting with a POST endpoint to add a new book to the library.

As usual, be sure to ask your trainer for help if you’re not sure of something, this is by no means trivial.

Build the website

Now that you’ve got the API, it’s time to start building the frontend of the website. Otherwise, people won’t actually be able to access the books they want!

We’re going to be using Vite – a dev server and bundler – to make getting everything working a bit easier. Open your terminal, go to your work directory and run

npm create vite@latest -- --template vanilla

NPM should offer to install Vite if you don’t already have it. It should prompt you for a name for the project, and then create a directory with the same name that should be populated with a basic template for a site. If you go to that directory, you should see a few different files. The ones you should pay most attention to are:

  • index.html – This is where the majority of the layout of the site is defined
  • main.js – This is where interactive and dynamic parts of the site are written
  • style.css – This contains the styling for your site

Currently, they’ll have some example content from the template. To preview the site, you enter the file that vite has created and install dependencies and run the dev server by running the following in your terminal:

npm install
npm run dev

Go to the address it shows in your browser, and a site should appear! Once you’re happy that’s working, open index.html. You’ll notice a div with id="app". Start building out a basic structure for your site, with a simple header and page layout, replacing the div that’s currently there. As you’re doing this, try to keep accessibility in mind and use correct semantic HTML elements when you can. Once you’ve got a basic structure, you can try styling it a bit too by editing style.css.

Adding content

You’ve got a basic structure for your site, but there’s no content yet! You’ve already made an API route for listing all the available books, so now you can put it to use. You’ll probably find it helpful to create a container div with a unique, descriptive id that you can use to put all the content in. If you open main.js, you’ll notice the code already there, which is used to add content to that original div that you replaced. Feel free to remove or comment out the code – make sure you leave the import './style.css' though!

Now, add some code to your main.js to call your API, retrieve the list of available books, and populate the container you made when the site loads. You’ll want to make use of document.querySelector to get the container element (you can look at the template code for an example of how to use it), and the Fetch API to make an HTTP request to your API. This should end up looking something like:

fetch("localhost:3001/books")
    .then(response => response.json())
    .then(data => setMyData(data));

Once you’ve done this, you should be able to refresh the page, and see all the books listed on your page.

Interactive sites

It’s good that people can see the available books, but how are they going to add new ones to the library? You should have an API endpoint for this too, so all you need is an interface to interact with it. Add a form to your page so that people can submit new books. You’ll want to use the form HTML tag. Have a look through the MDN docs for the form tag and see if you can figure out how to submit data to your API. Remember to make sure you’ve set the correct method to match what you defined for your API.

Once you’re done with that, you should be able to submit a book, refresh the page, and have it appear along with all the other books!