GoodReads Reviews

Book Reviews Web App

"Create an account, log in, search for, and leave reviews for your favorite books!"

Live | GitHub

Clean and simple welcome page

Project Brief:

Harvard CS50W Capstone Project

Time Frame:

August 2020 - October 2020

73.5 Hours Total

Role / Skills:

Lead Developer

Technologies Used:

    PERN Stack:
  • PostgreSQL
  • Express.js
  • React
  • Node.js
  • JavaScript
  • Local Storage, Session Storage
  • GoodReads API
  • Deployed to Heroku
  • Redux
  • React Router
  • React Bootstrap
  • SCSS
  • Axios
  • Converted from originally Python, Flask, Jinja2 project
  • CRUD capabilities - users can GET, POST, PUT, DELETE their own comment reviews of individual books


  • Users can log in to search for books, leave reviews, edit and delete their reviews, and see average reviews and review counts per book from Goodreads API data.
  • Express.js server-side fetch of Goodreads API endpoints as well as database queries using PostgreSQL for books, user reviews, and user login information.
  • Books imported from a CSV file of 5000 books
  • Environment variables stored via Heroku
  • Started as a course project from a Harvard Web Development course called CS50W, I originally created this application with Python, Flask, and Jinja2 according to the course. I then remade it using the PERN stack (PostgreSQL, Express.js, React, and Node.js). I used PostrgreSQL to store the users, books, and user reviews data. I set up Express.js for the GET, POST, PUT, DELETE CRUD capabilities for when the users leave reviews for individual books.

    One large challenge for this project was figuring out how to overcome some of React Router’s limitations when it came to dynamically pre-rendering pages when working with retrieved data. I had to figure out how to display the HTML template for the user reviews page first, before fetching the database and API data so that it can render that data onto the HTML page. I needed something that would work whenever the users refresh the page, navigate backwards and forwards, and type in the URL route directly to that specific book’s details page.

    In hindsight, I figured something like Gatsby or Next.js' getStaticProps for Static Generation would have been great for this, but I decided to test a few hypotheses with Express and found a workaround using the res.sendFile() response method to serve up the index.html file first, along with the necessary fetched data included in the options argument of the response. That way, the template HTML page is always served and rendered first in order for the data to be painted onto.

    Search via ISBN, title, or author of your favorite book!
    results page
    Results page loaded
    book details page
    Details per individual book imported from fetching from both PostgreSQL database and GoodReads API data

    Challenges Faced:

    Pre-rendering the "Details" HTML page first to display external fetched data

    After deploying the production version to Heroku, when refreshing or GET requesting the 'details' page of any book (/details/9958 route for example), I was stuck with a plain, white screen with only a JSON object of the API response data for that book. My Express.js routes were catching all of the GET requests before React could load the index.html page, therefore the application had no webpage to display the API data onto.

    I wanted my HTML to be pre-rendered in order for the fetched GoodReads API and database data to successfully "hydrate" and display on the page.

    To remedy this, I found a work-around on my server-side using the Express.js res.sendFile(path, options) method to pre-render the template index.html file as the path argument, along with the API response data object in the options argument.

    // detailsRouter.js
    let options = {
      headers: {
        'result': (results from GoodReads API fetch),
        'comment_list': (comments from the database fetch),
        'bookinfo': (book info from the database fetch)
    res.sendFile(path.join(__dirname, "../client/build/index.html"), options);

    Deploying to Heroku with the Heroku Postgres database add-on

    Heroku Postgres

    Before finally deploying the application on Heroku for production, I installed the Heroku Postgres add-on to host the PostgreSQL database for my users, books, and reviews. I made sure to include the DATABASE_URL environment variable provided by Heroku to link to the app's process.env and used conditional boolean logic to select either "deployment" or "production" mode configuration.

    This ensured the application was using either the "development mode" local database, or the "production mode" heroku hosted database.

    // db.js
    const devConfig = {
      user: process.env.PG_USER,
      host: process.env.PG_HOST,
      database: process.env.PG_DATABASE,
      password: process.env.PG_PASSWORD,
      port: process.env.PG_PORT
    const prodConfig = {
      connectionString: process.env.DATABASE_URL // <-- heroku add-on Postgres database URL
    const pool = new Pool(process.env.NODE_ENV === "production" ? prodConfig : devConfig)

    Final Result:

    Leave a review, edit your review, and delete your review seamlessly!
    goodreads gif