React Project - Idea to Production - Part Three - Building the Movie App using component library
Tags: react, redux, typescript, webpack
Published: 2020-06-18 03:47:10

This was originally posted [here](https://debojitroy.com/blogs/react-idea-to-production-part-three/)

This is the third post in the series. You can find the first post [here](https://dev.to/debojitroy/react-project-idea-to-production-part-one-wireframes-and-project-setup-b08)

## Where are we

Ok so till now we have

- Brainstormed on our brilliant idea to build a Movie App.
- We have decided what features are needed as part of the MVP.
- Our design team has given us the wireframes.
- We have setup our project as a Monorepo.
- We have setup linting rules, code formatter and commit hooks.
- We have setup our our component library
- We added support for Typescript in our component library
- We have setup Storybook
- We added our components to the component library
- We have added unit tests for our components
- We can see our components showcased in Storybook

## What are we going to do now

Ok so the next step is to build the movie app using the component library. We will be using [TMDB](https://www.themoviedb.org/?language=en-US) for fetching our movie details. We will maintain our application state using [Redux](https://redux.js.org/). We will use [Webpack](https://webpack.js.org/) to bundle our application. At the end of this post we should have converted our wireframes to an actual working website.

## TL;DR

This is a 4 part post

- [Part One : Wireframes and Project Setup](https://debojitroy.com/blogs/react-idea-to-production-part-one)

- [Part Two : Setting up a Component Library](https://debojitroy.com/blogs/react-idea-to-production-part-two/)

- Part Three : Building the Movie App using component library

- [Part Four: Hosting the Movie app and setting up CI/CD](https://debojitroy.com/blogs/react-idea-to-production-part-four/)

Source Code is available [here](https://github.com/debojitroy/movie-app)

Component Library Demo is available [here](https://d3f0roag7dlk8c.cloudfront.net/)

Movie App Demo is available [here](https://d1c9s36vd9mohd.cloudfront.net/)

## Extracting common functionality in `core`

It is always advisable to extract common services to keep it DRY. As we extracted common components in our previous post, we will extract common functionality in `core`.

### What resides in `core`

The definition of common functionality is very broad and there are more than one way to skin the chicken 🐔 For our project we will extract our api calls in `core`

### Setting up `core`

Move to the `packages` folder

```shell
cd packages
```

Create a new folder for our `core`

```shell
mkdir core
cd core
```

Initialise the yarn project

```shell
yarn init
```

Following the steps for naming, as we did in our previous [post](/blogs/react-idea-to-production-part-two/), our `package.json` looks like

```json
{
  "name": "@awesome-movie-app/core",
  "version": "1.0.0",
  "description": "Core Services for Awesome Movie App",
  "main": "index.js",
  "repository": "git@github.com:debojitroy/movie-app.git",
  "author": "Debojit Roy <debojity2k@gmail.com>",
  "license": "MIT",
  "private": true
}
```

## Building `core`

### Adding `axios`

We will be making a lot of XHR calls to fetch data. We can choose to use browser's native AJAX functionality or the shiny new `fetch` api. With so many browsers and different implementation of `fetch` it is safer not to use `fetch`. If we choose to include `fetch` we will have to add the required polyfills.

So it is much better to go ahead with `axios` which will make sure our network calls work correctly irrespective of the user's browser.

### Initialising `config` variables

As `core` is a common library, we don't want to hardcode, nor dictate how the environment variables are set. We would like to delegate it to the calling project to decide.

So we will create a `bootstrap` file which will be used to initialise the config.

```typescript
let config: { url: string; apiKey: string } = { url: "", apiKey: "" }

export const setConfig = (incomingConfig: { url: string; apiKey: string }) => {
  config = incomingConfig
}

export const getConfig = () => config
```

### Adding `search service`

One of the first things as per our requirement was to add a search service. We are going to use the [Search Endpoint](https://developers.themoviedb.org/3/search/search-movies)

After mapping the response, the functionality looks something like this

```typescript
import axios from "axios"
import isNil from "lodash/isNil"
import { getConfig } from "./bootstrap"

export interface SearchResult {
  popularity: number
  vote_count: number
  video: boolean
  poster_path: string
  id: number
  adult: boolean
  backdrop_path: string
  original_language: string
  original_title: string
  genre_ids: number[]
  title: string
  vote_average: number
  overview: string
  release_date: string
}

export interface SearchResponse {
  page: number
  total_results: number
  total_pages: number
  results: SearchResult[]
}

export const searchMovie = async (
  queryString?: string
): Promise<SearchResponse> => {
  const config = getConfig()

  if (isNil(queryString) || queryString.trim() === "") {
    return new Promise(resolve => {
      resolve({
        page: 1,
        total_pages: 1,
        total_results: 0,
        results: [],
      })
    })
  }

  const encodedQuery = encodeURI(queryString)

  const result = await axios.get(
    `${config.url}/3/search/movie?api_key=${config.apiKey}&query=${encodedQuery}`
  )

  return result.data
}
```

We will continue mapping rest of the functionality, the complete code is available [here](https://github.com/debojitroy/movie-app/tree/master/packages/core)

## Setting up Web Application

Now with the required services mapped out, we will focus on building the actual web application.

Splitting out code in this way helps to re-use functionality without copy pasting things over and over again.

Major parts of our webapp will be

- Public Files
- Webpack config
- Common parts
- Feature specific segregation

### WebApp Project setup

Move to the `packages` folder

```shell
cd packages
```

Create a new folder for our `webapp`

```shell
mkdir webapp
cd webapp
```

Initialise the yarn project

```shell
yarn init
```

Following the steps for naming, as we did in our previous [post](https://debojitroy.com/blogs/react-idea-to-production-part-two/), our `package.json` looks like

```json
{
  "name": "@awesome-movie-app/webapp",
  "version": "1.0.0",
  "description": "Web Application for Awesome Movie App",
  "main": "index.js",
  "repository": "git@github.com:debojitroy/movie-app.git",
  "author": "Debojit Roy <debojity2k@gmail.com>",
  "license": "MIT",
  "private": true
}
```

### Setting up `public` assets

So for the React project to mount, we need a DOM element, where React can take over and inject the elements. For this purpose we need a `index.html` file which will be served by the server before React takes over.

We will keep this `index.html` in our `public` folder, but feel free to choose any other name.

You can find the file [here](https://github.com/debojitroy/movie-app/tree/master/packages/webapp/public) Feel free to name the folder and files as you want, but make sure to update the same in the webpack config in the next step.

### Setting up Webpack

We will use `webpack` to package our application. You can choose any other packager for your project and make changes accordingly.

Prepare the `config` folder

```shell
mkdir config
```

#### Setting up shared configuration

For our `local` development we will be using `webpack dev server` and production build and minification for `production` build. But some of the steps will be common for both, we will extract those in our `common` config.

So our common config looks something like this

```javascript
// webpack.common.js
const path = require("path")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const HtmlWebPackPlugin = require("html-webpack-plugin")

const isEnvDevelopment = process.env.NODE_ENV === "development"
const isEnvProduction = process.env.NODE_ENV === "production"

module.exports = {
  entry: { main: "./src/entry/index.tsx" },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".jsx"],
  },
  node: {
    fs: "empty",
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|mjs|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
      {
        test: /\.css$/,
        use: [
          "style-loader",
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: isEnvDevelopment,
            },
          },
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              ident: "postcss",
              plugins: () => [
                require("postcss-flexbugs-fixes"),
                require("postcss-preset-env")({
                  autoprefixer: {
                    flexbox: "no-2009",
                  },
                  stage: 3,
                }),
                require("postcss-normalize"),
              ],
              sourceMap: isEnvProduction,
            },
          },
        ],
        // Don't consider CSS imports dead code even if the
        // containing package claims to have no side effects.
        // Remove this when webpack adds a warning or an error for this.
        // See https://github.com/webpack/webpack/issues/6571
        sideEffects: true,
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: ["file-loader"],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ["file-loader"],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebPackPlugin({
      title: "Awesome Movie App",
      template: "./public/index.html",
      filename: "./index.html",
      favicon: "./public/favicon.ico",
    }),
  ],
}
```

Most of the things are self explanatory. If you are new to webpack, I would suggest checking out their awesome [documentation](https://webpack.js.org/concepts/)

#### Setting up the `dev` config

With `common` config setup, we would like to setup our `dev` config. We want to use `webpack dev server` and `hmr` with routing fallback.

Our dev config looks like

```javascript
//webpack.dev.js
const path = require("path")
const merge = require("webpack-merge")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const common = require("./webpack.common.js")

module.exports = merge(common, {
  mode: "development",
  devtool: "inline-source-map",
  output: {
    path: path.join(__dirname, "../../dist/dist-dev"),
    filename: "[name].[contenthash].js",
    publicPath: "/",
  },
  devServer: {
    contentBase: "./dist-dev",
    historyApiFallback: true,
    allowedHosts: [".debojitroy.com"],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
  ],
})
```

### Building the `common` parts

Common parts are feature agnostic pieces which have the cross cutting functionality.

#### Common - Components

These are the common components which will be used across the features.

#### Common - Config

Configurations for applications which are defined here.

#### Common - Redux

Redux specific files will be stored here.

#### Common - Routes

Routing specific files will be stored here.

#### Common - Utils

Common utilities will be added here.

### Building Features

Features is where the actual features of the application will be kept. Think of each feature as a standalone piece of the application. Each feature in itself should be able to stand apart.
For demonstration purpose we will look into `SiteHeader` feature.

#### SiteHeader - Components

This part will contain all our React components as the name suggests. Based on the functionality required we will break down our feature in components.

#### SiteHeader - Redux

This is where all Redux related files will be added.

I am skipping over these sections fast as they are standard React / Redux stuff which are better explained in many other places.

### Getting the webapp running

#### Adding `.env`

We need to declare the config variables for running our application. In our production step we will be doing it differently. For local development let's add `.env` file and add it to `.gitignore` so that it doesn't get checked in.

Go to `webapp`

```shell
cd packages/webapp
```

Create a `.env` file

```shell
vim .env
```

Add the config values

```yml
API_URL=https://api.themoviedb.org
API_KEY=<Replace with actual key>
```

#### Preparing launch script

Now once we have `.env` setup, last thing we need to do is add the `start` script.

Open `package.json` inside `webapp` and add this under `scripts`

```json
"start": "cross-env development=true webpack-dev-server --config config/webpack.dev.js --open --port 8000"
```

## Running Webapp locally

Once we are done setting up `webapp`, lets try to run it locally.

First, build your `components`

```shell
cd packages/components
yarn build-js:prod
```

Second, build your `core`

```shell
cd packages/core
yarn build-js:prod
```

Finally start your `webapp`

```shell
cd packages/webapp
yarn start
```

If everything went well, you should see something like this

![Movie App Local Screencap](https://dev-to-uploads.s3.amazonaws.com/i/evp9gxijbpnzhchh7zo7.png)

Phew!!! That was a long one.

Now, the final step is to configure Continuous Integration and Deployment to make sure every-time we make changes, it gets deployed seamlessly. You can read about it in the [last instalment of this series](https://debojitroy.com/blogs/react-idea-to-production-part-four/).