/libsys

Library Management System using Typescript, Postgres and Docker.

Primary LanguageTypeScript

Library Management System

Simple API created to manage your library.

Contents

Overview

Non-functional Requirements

  1. Performance: The system should be optimized for reading operations since searching and listing books/borrowers will be frequent operations.
  2. Scalability: The system design should support the addition of new features in the future, like reservations or reviews.
  3. Security: Ensure that user inputs are validated to prevent SQL injection or other potential security threats.

Functional Requirements

1- Books:

  • Add a book with details like title, author, ISBN, available quantity, and shelf location.
  • Update a book’s details.
  • Delete a book.
  • List all books.
  • Search for a book by title, author, or ISBN.

2- Borrowers:

  • Register a borrower with details like name, email, and registered date (Keep the user details as simple as possible).
  • Update borrower’s details.
  • Delete a borrower.
  • List all borrowers.

3- Borrowing Process:

  • A borrower can check out a book. The system should keep track of which books are checked out and by whom.
  • A borrower can return a book.
  • A borrower can check the books they currently have.
  • The system should keep track of due dates for the books and list books that are overdue.

The Database

Design Considerations

  • The id for each table is consider a PK and reflects with the requirements as : book_id, borrower_id, borrowing_id
  • The Borrower/User can borrow 0 or more books, but one book must have at least one borrower.
  • 2 Borrowers can't borrow the same book <Book.id>
  • Shelf location is string indicating the location is the library, asuming number of locations aren't that big.
  • The quantity of books quantity is greater than or equal to zero, and borrowers can't borrow if book isn't exist nor available.
  • Borrowing Table is Junction table, made after normalizating the database, used to capture the relationship between the Borrower and Book tables in a many-to-many relationship.

Choosing the database

Both MySQL and PostgreSQL are excellent choices and meet the projects' requirements, but as a personal taste I will go with PostgreSQL as it's easier to setup for me and it supports out-of-the-box feaures like various data types.

Database Migrations

Database migrations are one important aspect when building scalable system, it allows automated deployments, rollbacks, versioning and team collabs. All the migrations are in database/migrations/sql

Getting started

You don't need anything to start, just docker-compose and you're ready to kick!

Running on development:

  • Use docker-compose.dev.yml : docker-compose -f docker-compose.dev.yml up -d --build && docker-compose -f docker-compose.dev.yml logs -f

Running on production:

  • Use docker-compose.prod.yml : docker-compose -f docker-compose.prod.yml --env-file .prod.env up -d --build

If you have postman installed, or you have an account, you can test it out : here

Features

While the project is pretty simple and it just meets the requirements, it's worth mentioning that is has some cool features beside the already defined ones in the requirements:

  • All CRUD operations are satisfied in a clean way
  • API has simple rate limitation an caching on GET routes
  • Database has indexes on most requested data as the system is mainly read heavy
  • It's dockerized and can be setuped in one command

Takes on Design

I have some takes on the design I came up with but for sake of limited time and loosy requirements. Following the requirements (bare minimal) forces me to make some weird routes:

  • How the system works, for now: there is no super user and normal user (Admin: add/delete books and manage other users), (Normal user: borrow and return books).
  • Borrow a book: /api/v1/books/borrow! as you can see it's on the books side which makes sense to me as it's more natural (book can be borrowed) same as Return!.
  • Paginations are implemented for some routes but not all, based on a quick estimation of how many records can be queried on a route, take this: User can see the books they borrowed (no need to add paginations, I guess)
  • Show borrowed books: /api/v1/borrowers/<user_id>/books, can you see it ?
  • Search for books: /api/v1/books/search?title=the&author=charles%dickens&genre=literature&isbn=978&limit=10&page=2 combining params - will get all the books which have "the" in the title by Charles Dickens on genre: Literature and so on.
  • Getting overdue books: wellll is it better to keep it separate from the /books ?
  • Usually updating email isn't realistic!

Notes

  • Currently, I am throwing database errors as server errors : 500
  • npm run dev won't work as the database is running in the container, run : docker-compose -f docker-compose.dev.yml up -d --build && docker-compose -f docker-compose.dev.yml logs -f instead.
  • There are some unhandled cases like what happens when the user returns a book that has been deleted
  • I know that throwing errors are expensive and should be used a lot specially in JS world

Todo

  • Desgin an archicture to follow
  • Database
    • Choose a database and why ?
    • Schema Desgin
    • Normalization
    • Choosing indexes
  • API Desgin: REST
    • Book routes
    • Borrower routes
  • Security : Authorization and Authentication
  • Documentations
  • Production Setup: HTTPs, Docker
  • Define a way to handle errors : throw the errors from the service with status codes and check types on controller
  • Testing
  • Use absoulte paths in TS
  • separate server errors from db errors