Skip to content

Full Stack Solo Project: Javascript-React-Redux-Hooks-Express-Sequelize. This project learn from goodreads and explore reader experience with shelves and individual books. It allow reader to review, rate, and add or delete books from their bookshelves. Reading status of reading, wants to read and browsing are also included. Reader profile also e…

Notifications You must be signed in to change notification settings

xxl4tomxu98/Mybooks-React-Redux

Repository files navigation

Solo React Project

This is the repository for the Solo React project.

Live Link

My-Books

Table of Contents

Technologies

  • Back End
    • Express Node.js pg Sequelize
  • Front End
    • JavaScript React Redux Hooks

Model Schema

Model schema

Installation

  1. Clone this repository

    git clone`https://github.com/xxl4tomxu98/Mybooks-React-Redux.git`
  2. Install dependencies (npm install)

    $ npm install
  3. Create a .env file based on the example with proper settings for your development environment

    - CREATE USER mybooks_app WITH PASSWORD <<good password>>
    - CREATE DATABASE mybooks_db WITH OWNER mybooks_app
    
  4. Setup your PostgreSQL user, password and database and make sure it matches your .env file with CREATEDB privileges

    PORT=8080
    DB_USERNAME=mybooks_app
    DB_PASSWORD=<<your good password>>
    DB_DATABASE=mybooks_db
    DB_HOST=localhost
    JWT_SECRET=<<good secret code>>
    JWT_EXPIRES_IN=604800 (**about a week**)
    
  5. Run

    • npm run db:create npx dotenv sequelize db:create
    • npm run db:migrate npx dotenv sequelize db:migrate
    • npm run db:seed:all npx dotenv sequelize db:seed:all
    • npm start

browse to http://localhost:8080


Features

Add Books to Custom Bookshelf


Books can be added to bookshelves two ways: the dropdown from /user/shelves page where the bookshelves are located, or the books details page (/books/:bookId) after searching for a particular book. It's important to highlight that books can not appear multiple times on the same bookshelf. This is ensured by doing a query in the Back End /api/books/:bookid/shelves route for the bookshelf dropdown select field that filters out bookshelves the books already belongs to.

// api/books.js file

//code grabs all shelves for user with book and all shelves in db then filters out all shelves by excluding
//the shelves found for the user with the book

    router.get("/:bookid/shelves",
    authenticated,
    asyncHandler(async (req, res) => {
    //get all the open shelves where the book is not currently on
    const bookId = req.params.bookid;
    //all shelves for the user that have the specified book
    const shelves = await Shelf.findAll({
      where: {
        userId : req.user.id,
      },
      include: {
        model: Book, where: {
          id: bookId
        }
      }
    });

    //all shelves in db for the user
    const allShelves = await Shelf.findAll({
      where: {
        userId: req.user.id
      }
    });
    //shelf id's for all user shelves with specific book
    let includedShelf = [];
    for (let shelf of shelves) {
      includedShelf.push(shelf.id);
    };
    //array of all shelves in db
    let allShelvesArray = [];
    for (let shelf of allShelves) {
      allShelvesArray.push(shelf);
    };

    //filters array for all shelves in db to have all
    //shelves that don't contain the book already
    const allShelvesWithoutBook = allShelvesArray.filter(function(shelf) {
      if (!includedShelf.includes(shelf.id)) {
        return shelf;
      }
    });
      //return as an obj containing the filtered array of objects
    res.json(allShelvesWithoutBook);
}));

Note:

  • All the shelves for the user are found first. Then the list of shelves with the book included. That list is then converted to id's which is easier to manage later opposed to the shelf object from the db. The array of id's are then used to filter out all of the bookshelves that include that id by cross referencing them with the original query of all the user bookshelves.
  • Frontend components/BookDetail.js matches selected shelf name and converted to shelfId for React/Redux treatment
handleSubmit = async (event) => {

    event.preventDefault();
    try {
      const bookId = this.props.match.params.id;
      const shelfName = this.state.selection;
      const shelves = this.props.openShelves;
      const found = shelves.find(shelf => shelf.name === shelfName);
      const shelfId = found.id;
      await this.props.getShelfDetail(shelfId);
      await this.props.addBookToShelf(bookId, shelfId);
      this.props.history.push(`/shelves/${shelfId}`);
    } catch(e) {}
  }

Search Feature


The Back End /api/books route handles the search feature. The searchbar encompases the form:

  //- searchbar.js file
    const [term, setTerm] = useState('');

    const history = useHistory();

    const updateTerm = (e) => {
        setTerm(e.target.value);
    }

    const handleSubmit = (e) => {
        e.preventDefault();
        searchBooks(term);
    }

    const searchBooks = async (term) => {
        const res = await fetch(`/api/books/search/${term}`)
        if (res.ok) {
            const data = await res.json();
            history.push({
              pathname: '/search',
              state: data,
            });
            return data;
        }
        throw res;
    }

The API to route /api/books, which accepts a GET req with the search term as parameter in the end point, gets extracted. The search term is then use to query all book resources in the db that house the search term in it's title, case insensitive ofcourse.

/api/books.js
router.get('/search/:term', asyncHandler(async (req, res) => {
  //const { term } = req.query;
  let term = req.params.term;
  let books;
  if (term) {
    try {
      books = await Book.findAll({
        where: {
          [Op.or]: [
            { title: { [Op.iLike]: `%${term}%` } },
            { author: { [Op.iLike]: `%${term}%` } }
          ]
        },
        order: [['title', 'ASC']]
      });
    } catch (err) {
      console.log(err);
    }
  } else {
    books = await Book.findAll({
      limit: 30,
      order: [['title', 'ASC']]
    })
  }
  //const searchTitle = `Search result for "${term}"`
  //res.render('searchpage', { books, searchTitle });
  res.json(books);
}))

Deploy to Heroku

  1. Create a new project
  2. Under Resources click "Find more add-ons" and add the add on called "Heroku Postgres"
  3. Install the Heroku CLI
  4. Run $ heroku login
  5. Add heroku as a remote to this git repo $ heroku git:remote -a mybooks-react-redux
  6. Push the project to heroku $ git push heroku master
  7. Connect to the heroku shell and prepare your database
    $ heroku run bash
    $ sequelize-cli db:migrate
    $ sequelize-cli db:seed:all

(You can interact with your database this way as youd like, but beware that db:drop should not be run in the heroku environment)

  1. Add a REACT_APP_BASE_URL config var. This should be the full URL of your react app: i.e. "https://mybooks-react-redux.herokuapp.com"

  2. profit

About

Full Stack Solo Project: Javascript-React-Redux-Hooks-Express-Sequelize. This project learn from goodreads and explore reader experience with shelves and individual books. It allow reader to review, rate, and add or delete books from their bookshelves. Reading status of reading, wants to read and browsing are also included. Reader profile also e…

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published