TypeScript Tutorial: Building a Simple Interactive Book Library App

Are you a developer looking to build a more organized and efficient way to manage your reading list? Do you want to learn TypeScript by creating a practical, hands-on project? This tutorial will guide you through building a simple, interactive book library application using TypeScript. You’ll learn fundamental TypeScript concepts and apply them to create a functional app where users can add, view, and manage their books.

Why Build a Book Library App?

Managing books can be a challenge. Whether you’re a casual reader or a book enthusiast, keeping track of what you’ve read, what you want to read, and your ratings can become overwhelming. A digital book library app provides a streamlined solution. It allows you to:

  • Organize your books digitally
  • Keep track of reading progress
  • Rate and review books
  • Search and filter your collection

Building this app also offers an excellent opportunity to learn TypeScript. You’ll gain practical experience with types, interfaces, classes, and other core TypeScript features.

Prerequisites

Before you begin, make sure you have the following:

  • A basic understanding of HTML, CSS, and JavaScript.
  • Node.js and npm (Node Package Manager) installed on your system.
  • A code editor (e.g., Visual Studio Code, Sublime Text).

Setting Up Your Project

Let’s get started by setting up the project. First, create a new directory for your project and navigate into it using your terminal:

mkdir book-library-app
cd book-library-app

Next, initialize a new Node.js project:

npm init -y

This command creates a package.json file, which will manage your project’s dependencies.

Installing TypeScript

Now, install TypeScript as a development dependency:

npm install --save-dev typescript

Next, initialize a TypeScript configuration file (tsconfig.json):

npx tsc --init

This command generates a tsconfig.json file with default settings. You can customize this file to configure how TypeScript compiles your code. For this project, we’ll keep the default settings, but you might want to adjust them later based on your needs.

Project Structure

Let’s create the basic project structure. Create the following directories and files within your project directory:

  • src/: This directory will contain your TypeScript source files.
    • models/: This directory will contain your data models.
    • Book.ts
    • app.ts: This is the main file for your application logic.
  • index.html: This is the main HTML file for your application.
  • style.css: This file will contain your CSS styles.

Your project structure should look like this:

book-library-app/
├── index.html
├── style.css
├── package.json
├── tsconfig.json
└── src/
    ├── models/
    │   └── Book.ts
    └── app.ts

Creating the Book Model (Book.ts)

In the src/models/Book.ts file, we’ll define a class to represent a book. This class will include properties like title, author, and whether the book has been read.

// src/models/Book.ts

export class Book {
  title: string;
  author: string;
  isRead: boolean;
  rating?: number; // Optional property

  constructor(title: string, author: string, isRead: boolean = false, rating?: number) {
    this.title = title;
    this.author = author;
    this.isRead = isRead;
    this.rating = rating;
  }
}

Explanation:

  • export class Book: This defines a class named Book that can be used in other parts of your application. The export keyword makes the class accessible from other modules.
  • title: string;, author: string;, isRead: boolean;: These lines declare properties of the Book class. TypeScript uses types (like string and boolean) to ensure that the values assigned to these properties are of the correct type.
  • rating?: number;: This declares an optional property rating of type number. The ? indicates that this property may or may not be present for a given book.
  • constructor(title: string, author: string, isRead: boolean = false, rating?: number): This is the constructor for the Book class. It’s used to initialize a new Book object. The constructor takes the title and author as required parameters, and isRead is set to false by default. The optional rating is also included.
  • this.title = title;, this.author = author;, this.isRead = isRead;, this.rating = rating;: These lines assign the values passed to the constructor to the corresponding properties of the Book object.

Building the Application Logic (app.ts)

In the src/app.ts file, we’ll implement the core logic of our book library app. This includes adding books, displaying the book list, and handling user interactions.

// src/app.ts
import { Book } from './models/Book';

// Sample books
const books: Book[] = [
  new Book('The Lord of the Rings', 'J.R.R. Tolkien', true, 5),
  new Book('Pride and Prejudice', 'Jane Austen', false),
];

// DOM elements
const bookList = document.getElementById('bookList') as HTMLUListElement | null;
const addBookForm = document.getElementById('addBookForm') as HTMLFormElement | null;
const bookTitleInput = document.getElementById('bookTitle') as HTMLInputElement | null;
const bookAuthorInput = document.getElementById('bookAuthor') as HTMLInputElement | null;

// Function to render the book list
function renderBooks(): void {
  if (!bookList) return;
  bookList.innerHTML = ''; // Clear previous list
  books.forEach((book) => {
    const li = document.createElement('li');
    li.innerHTML = `
      <span>${book.title} by ${book.author}</span>
      <button class="read-button">${book.isRead ? 'Mark Unread' : 'Mark Read'}</button>
      <button class="delete-button">Delete</button>
    `;
    const readButton = li.querySelector('.read-button') as HTMLButtonElement;
    const deleteButton = li.querySelector('.delete-button') as HTMLButtonElement;

    if (readButton) {
      readButton.addEventListener('click', () => {
        book.isRead = !book.isRead;
        renderBooks(); // Re-render the list
      });
    }

    if (deleteButton) {
      deleteButton.addEventListener('click', () => {
        const bookIndex = books.indexOf(book);
        if (bookIndex !== -1) {
          books.splice(bookIndex, 1);
          renderBooks(); // Re-render the list
        }
      });
    }

    bookList.appendChild(li);
  });
}

// Function to add a new book
function addBook(title: string, author: string): void {
  const newBook = new Book(title, author);
  books.push(newBook);
  renderBooks();
}

// Event listener for the add book form
if (addBookForm) {
  addBookForm.addEventListener('submit', (event: Event) => {
    event.preventDefault(); // Prevent form submission
    if (!bookTitleInput || !bookAuthorInput) return;
    const title = bookTitleInput.value;
    const author = bookAuthorInput.value;
    if (title.trim() && author.trim()) {
      addBook(title, author);
      // Clear the input fields
      bookTitleInput.value = '';
      bookAuthorInput.value = '';
    }
  });
}

// Initial render
renderBooks();

Explanation:

  • import { Book } from './models/Book';: This line imports the Book class from the Book.ts file.
  • const books: Book[] = [...]: This declares an array named books to store the book objects. It is initialized with a sample book. The type annotation Book[] indicates that this array will hold objects of type Book.
  • const bookList = document.getElementById('bookList') as HTMLUListElement | null;: This line retrieves the HTML element with the ID ‘bookList’. The as HTMLUListElement | null part is a type assertion. It tells TypeScript that we expect this element to be an HTML unordered list (<ul>) or null. This is important for type safety and to avoid potential errors. The same logic is applied to other DOM elements.
  • function renderBooks(): void { ... }: This function is responsible for rendering the book list in the HTML. It iterates over the books array and creates HTML list items (<li>) for each book.
  • bookList.innerHTML = '';: This clears the existing content of the bookList element before re-rendering the list. This prevents duplicate entries when the list is updated.
  • books.forEach((book) => { ... });: This iterates through each book in the books array.
  • Inside the forEach loop:
    • const li = document.createElement('li');: Creates a new list item (<li>) element for each book.
    • li.innerHTML = ...: Sets the HTML content of the list item. This includes the book title, author, and buttons for marking the book as read/unread and deleting the book.
    • Event listeners are attached to the buttons to handle user interactions (mark as read/unread, delete).
    • bookList.appendChild(li);: Appends the list item to the bookList element.
  • function addBook(title: string, author: string): void { ... }: This function adds a new book to the books array and then calls renderBooks() to update the display.
  • The code then sets up an event listener for the form submission. When the form is submitted, it prevents the default form submission behavior, retrieves the title and author from the input fields, and adds the book. It also clears the input fields after adding the book.
  • renderBooks();: This line calls the renderBooks() function to initially render the book list when the page loads.

Creating the HTML (index.html)

Now, let’s create the HTML file (index.html) to structure the user interface of your application.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Book Library</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <h1>My Book Library</h1>

    <div class="add-book-form">
      <form id="addBookForm">
        <label for="bookTitle">Title:</label>
        <input type="text" id="bookTitle" name="bookTitle" required><br>

        <label for="bookAuthor">Author:</label>
        <input type="text" id="bookAuthor" name="bookAuthor" required><br>

        <button type="submit">Add Book</button>
      </form>
    </div>

    <ul id="bookList">
      <!-- Book list will be displayed here -->
    </ul>
  </div>

  <script src="app.js"></script>
</body>
</html>

Explanation:

  • <!DOCTYPE html>: Declares the document as HTML5.
  • <html lang="en">: The root element of the HTML page, specifying the language as English.
  • <head>: Contains meta-information about the HTML document, such as the title and links to external resources (CSS).
  • <meta charset="UTF-8">: Specifies the character encoding for the document.
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">: Configures the viewport for responsive design.
  • <title>Book Library</title>: Sets the title of the HTML page, which appears in the browser tab.
  • <link rel="stylesheet" href="style.css">: Links the external CSS file (style.css) to style the page.
  • <body>: Contains the visible page content.
  • <div class="container">: A container to hold the content.
  • <h1>My Book Library</h1>: The main heading of the application.
  • <div class="add-book-form">: A container for the add book form.
  • <form id="addBookForm">: The form to add a new book.
  • <label> and <input> elements: Provide labels and input fields for the book title and author.
  • <button type="submit">Add Book</button>: The submit button for the form.
  • <ul id="bookList">: An unordered list where the book list will be displayed. The JavaScript code will populate this list with book entries.
  • <script src="app.js"></script>: Links the compiled JavaScript file (app.js) to the HTML page. This is where your TypeScript code will be executed after it is compiled.

Adding Styles (style.css)

Let’s add some basic styles to make the app look better. Create a style.css file and add the following:

/* style.css */
body {
  font-family: sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f4f4f4;
}

.container {
  width: 80%;
  margin: 20px auto;
  background-color: #fff;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

h1 {
  text-align: center;
  color: #333;
}

.add-book-form {
  margin-bottom: 20px;
}

label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

input[type="text"] {
  width: 100%;
  padding: 8px;
  margin-bottom: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #3e8e41;
}

#bookList {
  list-style: none;
  padding: 0;
}

#bookList li {
  padding: 10px;
  border-bottom: 1px solid #eee;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.read-button, .delete-button {
  margin-left: 10px;
  padding: 8px 12px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  color: white;
}

.read-button {
  background-color: #008CBA;
}

.read-button:hover {
  background-color: #0077a0;
}

.delete-button {
  background-color: #f44336;
}

.delete-button:hover {
  background-color: #da190b;
}

Compiling and Running Your Application

Now, let’s compile your TypeScript code and run the application.

Open your terminal and navigate to the project directory. Then, run the following command to compile your TypeScript code:

npx tsc

This command will compile your .ts files into .js files in the same directory. You should now have an app.js file.

To run the application, open index.html in your web browser. You should see the basic layout of your book library app. You can add books using the form, and they will be displayed in the list. Clicking the “Mark Read” button will change the book’s status, and clicking the “Delete” button will remove the book.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to fix them when working with TypeScript and this type of application:

  • Type Errors: TypeScript’s type system can catch many errors at compile time. If you encounter type errors, carefully review the error messages in your terminal or code editor. Make sure that the data types you’re using are consistent and that your code adheres to the type definitions.
  • DOM Element Selection Issues: When using document.getElementById() or other DOM selection methods, ensure that the element exists in your HTML. Use type assertions (e.g., as HTMLInputElement | null) to help TypeScript understand the expected type and to handle the case where an element might not be found (by checking for null).
  • Event Listener Problems: Make sure that your event listeners are correctly attached to the elements. Double-check that you’re selecting the correct elements and that your event handler functions are defined properly.
  • Incorrect Paths: Ensure that the file paths in your import statements are correct. Incorrect paths can lead to errors when your code tries to import modules.

Key Takeaways

  • Types: TypeScript introduces static typing, which helps you catch errors early and write more maintainable code.
  • Classes: Classes are a fundamental part of TypeScript, allowing you to create reusable and organized code through object-oriented programming (OOP) principles.
  • Interfaces: Interfaces define contracts for the structure of your objects.
  • DOM Manipulation: You can interact with HTML elements using JavaScript and TypeScript to create dynamic and interactive web applications.
  • Event Handling: Event listeners enable you to respond to user interactions, such as button clicks and form submissions.

FAQ

  1. Can I store the book data in local storage? Yes, you can extend this application to store the book data in local storage, so it persists even when the user closes the browser. You would need to use the localStorage API to save and retrieve the book data.
  2. How can I add more features to the app? You can add features like sorting books by title or author, filtering books based on read status, adding book covers, and more.
  3. How do I handle errors? You can add error handling to the application by using try...catch blocks to catch potential errors, such as those that might occur when parsing data from local storage or making network requests. You can also display error messages to the user.
  4. How can I deploy this application? You can deploy the application to a platform like Netlify or GitHub Pages. You’ll need to build the project (npx tsc) and then deploy the resulting HTML, CSS, and JavaScript files.
  5. What are some good practices for larger projects? For larger projects, consider using a module bundler like Webpack or Parcel to manage dependencies and optimize your code for production. Also, consider using a state management library like Redux or Zustand if your application’s state becomes complex.

Building a book library app with TypeScript is a fantastic way to learn the fundamentals of the language and apply them to a practical project. By following this tutorial, you’ve gained experience with core TypeScript concepts such as types, classes, and DOM manipulation. You’ve also learned how to structure a project, handle user input, and build a simple, interactive web application. This is just the beginning. The skills you’ve acquired will serve as a solid foundation for your journey into TypeScript development. Keep experimenting, keep practicing, and you’ll continue to grow your abilities and expand your knowledge in the world of web development.