Category Archives: TUTORIALS

Detailed tutorials explaining how to build most common applications, in different languages.

Creating loader component and set up one CSS per brand.

branch-name:  
Click To Copy

 

Previous solution was great but not perfect. We still add a CSS of two brands in one CSS file and loading all CSS for all brands per each component.

Could we do it better and load only the necessary CSS for each component for the particular branch ? Yers we could. The idea about bundle splitting is that one CSS file will be created per each component.

So in order to have one CSS file per brand we have to

Create a wrapper component per each brand.

Let’s start with

The Home component.

We are going to move the component’s code from index.js to a new file called renderer.js and repurpose index.js to be a wrapper component, which will load brand specific sub component, that will pass the style object back to the renderer component in renderer.js. If it sounds a bit confusing don’t worry. Follow that tutorial and it will get clear.

./src/components/Home/renderer.js

import React from 'react';

const Renderer = ({styles, title}) => {
  return (
    <div>
      <div className={styles.wrapper}>{title}</div>
    </div>
  );
}

export default Renderer;

The code in renderer file is pretty similar to that we had in index.js with one exception: we are going to pass the css as a property to this component.
And just to demonstrate how we could render different layout per each brand we are going to pass the title property as well.

Now the index.js will become our Home component wrapper, which will dynamically load either ./brands/one or ./brands/two sub component, which on the other hand will load our ./renderer.js component, passing the appropriate CSS for the selected brand.

./src/components/Home/index.js

import React from 'react';
import Loadable from 'react-loadable';
import Loading from '../Loading';

const one = Loadable({
  loader: () => import ('./brands/one'),
  loading: Loading
});

const two = Loadable({
  loader: () => import ('./brands/two'),
  loading: Loading
});

const components = {
  one,
  two
}

const Home = ( {subDomain} ) => {
  const Component = components[subDomain];
  return (
      <Component />
  )
}
export default Home;

what we just did:
– we crated a wrapper component, that will conditionally load the helper component for the current brand (lines 5 and 10)
-we render the appropriate sub component, based on the brand name.

Let’s create the helper sub components that will load the brand specific CSS and pass it to the renderer component and render it.

These components will look quite similar:

./src/components/Home/brands/one/index.js

import React from 'react';
import styles from './styles.scss';
import Renderer from '../../renderer.js'

export default () => {
  return (
    <Renderer styles={styles} title="This is my home section rendered for One!" />
  )
}

./src/components/Home/brands/two/index.js

import React from 'react';
import styles from './styles.scss';
import Renderer from '../../renderer.js'

export default () => {
  return (
    <Renderer styles={styles} title="This is my home section rendered for Two!" />
  )
}

what we just did:
– we imported brand specific CSS in each of the components (line 2)
– imported the renderer component (line 3)
– rendered the renderer component, passing the CSS and the title property (line 7)

Open the ./dist folder and look at 1.css and 2.css contents:

./dist/1.css

.one-wrapper{background-image:url(/dist/images/b5c0108b6972494511e73ad626d1852f-home.png);height:500px}.one-wrapper h2{color:#000}

./dist/2.css

.two-wrapper{background-image:url(/dist/images/a005b97826d5568577273d214dd5f89a-home.png);height:800px}.two-wrapper h2{color:#00f;font-size:50px}

Webpack created two files with the corresponding CSS: one-wrapper and two-wrapper containing only the CSS needed for each brand.

Open the browser and give it a try. The result should be what we saw in the previous chapter, but this time only the brand specific CSS is loaded.

Nice! Now we have Webpack created these two CSS files, but the class names are one-wrapper and two-wrapper which comes from the lead folder name, which now instead of been ./Home is ./Home/one and /Home/two  What will happen if we want to make another component brand specific?

The Greetings component

Let’s do the same changes:

./src/components/Greetings/renderer.js

import React, { Component } from 'react';
import { connect } from 'react-redux';


const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

class Greetings extends Component {

  constructor(props) {
    super(props); 
  }
  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.props.changeUserName(newName);
    this.props.toggleLogInPopup();
  }

  usernameChanged(el) {
    let newName = el.target.value;    
    this.props.changeUserName(newName);
  }

  onToggleEditMode() {
    this.props.toggleLogInPopup();
  }

  render() {
    let element = <h2 onClick={() =>{   this.onToggleEditMode()  }}>Hello:  {this.props.userName}</h2>;
    if(this.props.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.props.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
      <div>
        <div className={this.props.styles.wrapper}>
          {element}
        </div>
      </div>);
  }
}

const mapStateToProps = ( storeState ) => {
  return {  
    userName: storeState.user.userName,
    editMode: storeState.user.editMode
  }
}

const mapDispatchToProps = dispatch => {
  return {
    toggleLogInPopup: () => {
      dispatch({type: TOGGLE_EDIT_MODE});
    },
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Greetings);

what we just did:
– we just moved the code from ./index.js to ./renderer.js
– we removed the CSS import since we are passing CSS as a property
– we changed the div class name to come from the passed property (line 36)

The rest is the same like in Home component.

The index component will actually look exactly the same:

./src/components/Greetings/index.js

import React from 'react';
import Loadable from 'react-loadable';
import Loading from '../Loading';



const one = Loadable({
  loader: () => import ('./brands/one'),
  loading: Loading
});

const two = Loadable({
  loader: () => import ('./brands/two'),
  loading: Loading
});

const components = {
  one,
  two
}

const Home = ( {subDomain} ) => {

  const Component = components[subDomain];
  return (
      <Component />
  )
}
export default Home;

Let’s load different ‘home’ pictures for each brand.
Move the home picture in ./images/home.png to ./images/one/home.png and add another image for ./images/two/home.png (you could either download some png or use the one in this branch)

./src/components/Greetings/brands/one/styles.scss

.wrapper {
  background-image:url('../../../../images/one/home.png');
  height: 500px;
  h2 {
    color: black;
  }
}

./src/components/Greetings/brands/two/styles.scss

.wrapper {
  background-image:url('../../../../images/two/home.png');
  height: 800px;
  h2 {
    color: blue;
    font-size: 50px;
  }
}

Here we have to adjust the relative path to the images since this component goes two levels deeper and we moved the images into a brands folders (line 2)

And the helper sub components are the same like in Home component.

./src/components/Greetings/brands/one/index.js

import React from 'react';
import styles from './styles.scss';
import Renderer from '../../renderer.js'

const One = () => {
  return (
    <Renderer styles={styles} />
  )
}

export default One;

./src/components/Greetings/brands/two/index.js

import React from 'react';
import styles from './styles.scss';
import Renderer from '../../renderer.js'

const One = () => {
  return (
    <Renderer styles={styles} />
  )
}

export default One;

Start the server and go to http://one.localhost:3006/home and you will notice that the Home component height increased. Why this happened?

Let’s open http://one.localhost:3006/dist/1.css and look at the class names:

.one-wrapper{background-image:url(/dist/images/b5c0108b6972494511e73ad626d1852f-home.png);height:500px}.one-wrapper h2{color:#000}

Somehow the one-wrapper has background-image:url(/dist/images/b5c0108b6972494511e73ad626d1852f-home.png) and height:500px that belongs to the Greetings component.

Why this is happening? Because of the way how we set up class name structure in Css-Loader. If you look at webpack.base.config.js  you will see that the localIdentName which is the CSS className is constructed by adding the folder name, and the actual local identifier ‘[folder]-[local]’

 ./src/webpack.base.config.js

...
      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
...

But the folder name now is ‘one’ or ‘two’ for both components since it takes only the leaf folder name. Let’s fix this by

Make brand component names unique.

Go to src/Home/brands/one and rename it to src/Home/brands/OneHome and src/Home/brands/two to be src/Home/brands/TwoHome
and do the same for the greetings component: src/Greetings/brands/one => src/Greetings/brands/OneGreetings and src/Greetings/brands/Two => src/Greetings/brands/TwoGreetings

Next let’s make the appropriate changes in both: Home and Greeting component:

./src/Home/index.js

import React from 'react';
import Loadable from 'react-loadable';
import Loading from '../Loading';

const one = Loadable({
  loader: () => import ('./brands/OneHome'),
  loading: Loading
});

const two = Loadable({
  loader: () => import ('./brands/TwoHome'),
  loading: Loading
});

const components = {
  one,
  two
}

const Home = ( {subDomain} ) => {

  const Component = components[subDomain];
  return (
      <Component />
  )
}
export default Home;

and

./src/Greetings/index.js

import React from 'react';
import Loadable from 'react-loadable';
import Loading from '../Loading';



const one = Loadable({
  loader: () => import ('./brands/OneGreetings'),
  loading: Loading
});

const two = Loadable({
  loader: () => import ('./brands/TwoGreetings'),
  loading: Loading
});

const components = {
  one,
  two
}

const Home = ( {subDomain} ) => {

  const Component = components[subDomain];
  return (
      <Component />
  )
}
export default Home;

Run the project and check /dist/3.css 

.OneHome-wrapper--t4U5b{background:#8d8dac;color:#fff;text-align:center;font-family:MyFont}

it contains only CSS for the Home component.

Adding a hash in CSS class names

As an extra step we could also add a hash for each class name. This will make class names unique per component, so if accidentally happened to have two components with the same names their CSS won’t colide.

This could be achieved by simply adding a hash directive in localIdentName in CSS-loader config [folder]-[local]–[hash:base64:5]

const getEnvironmentConstants = require('./getEnvironmentConstants');
const webpack =require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',    
    './src/index.js',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist/',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },

      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]--[hash:base64:5]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      },
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      },
      //File loader used to load fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }                    
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } ),  
  ]
};

Run the project again and the home component should look like before. Open http://one.localhost:3006/dist/1.css and you will see how the has was appended to the class names.

branch-name:  
Click To Copy

 

Maximum Subarray

Task

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input:

 [-2,1,-3,4,-1,2,1,-5,4],

Output:

 6

Explanation:

 [4,-1,2,1] has the largest sum = 6.

This problem was taken from Leetcode

Solution

The solution:

Brut force.

The straight forward solution could be to iterate through all elements in the array and calculate the subarray values and compare them.

In the example above[-2,1,-3,4,-1,2,1,-5,4] we will do:

starting index: 1
-2 = -2
-2, 1 = -1
-2, 1, -3 = -4
-2, 1, -3, 4 = 0
-2, 1, -3, 4, -1 = -1
-2, 1, -3, 4, -1, 2 = 1
-2, 1, -3, 4, -1, 2, 1 = 2
-2, 1, -3, 4, -1, 2, 1, -5 = -3
-2, 1, -3, 4, -1, 2, 1, -5, 4 = 1

starting index: 2
1 = 1
1, -3 = -2
1, -3, 4 = 2
1, -3, 4, -1 = 1
1, -3, 4, -1, 2 = 3
1, -3, 4, -1, 2, 1 = 4
1, -3, 4, -1, 2, 1, -5 = -1
1, -3, 4, -1, 2, 1, -5, 4 = 3

starting index: 3
-3 = -3
-3, 4 = 1
-3, 4, -1 = 0
-3, 4, -1, 2 = 2
-3, 4, -1, 2, 1 = 3
-3, 4, -1, 2, 1, -5 = -2
-3, 4, -1, 2, 1, -5, 4 = 2

starting index: 4
4 = 4
4, -1 = 3
4, -1, 2 = 5
4, -1, 2, 1 = 6
4, -1, 2, 1, -5 = 1
4, -1, 2, 1, -5, 4 = 5

… and so on till the last element in the array.

So the winner clearly is 4, -1, 2, 1 = 6, but this approach will take a lot of repetitions. Interestingly there is a linear solution called: Kadane’s algorithm.

Using Kadene’s algorithm.

The basic idea of this algorithm is to break the array into a sets of mutually exclusive sets, calculate their sums and find the largest one.

First let’s look closely of what we are doing to find the maximum sum using brut force. We are splitting the array to a sets of all possible contiguous sub arrays and we calculate their sum. This means that:
– if the array contains only negative values we don’t really need to split the array cause the answer will be the largest value in the array. i.e. [-1,-5,-3] = -1 (the one close to 0)
– if this is a mixed array with negative and positive values the max sum of contiguous sub array will be > 0 so we could ignore any case where the sum is negative.

This way we could iterate through each element of the array nums[i]  where i is the index of the element in the array (starting with the first one nums[0]), and calculate the sum (let’s call it max_here = max_here + nums[i] ).
If we get a negative result we already know for sure that this is not what we are looking for and we set up max_here to the next element in the array max_here = nums[i]

So in the example above: [-2,1,-3,4,-1,2,1,-5,4]
We are starting by setting up both params to the first element in the array: max_here =max_so_far = nums[0] . We are going to use max_so_far to store the maximum sum discovered so far, and max_here to calculate the maximum sum so far. Once again if the max_sum is negative, we just set it up to be equal to the next element in the array nums[i] so on the next iteration max_sum = nums[i-1] + nums[i]

Starting with setting up max_here = max_so_far = nums[0] = -2

i nums[i]    action described max_here max_so_far
1 1  sum = max_here + nums[1], which is:
sum = -2 + 1 = -1 which is smaller than nums[1] so max_here = nums[1] = 1 (line 10 in the code snipped below)
and since max_here > max_so_far,  max_so_far = max_here =1 (line 11)
1 1
2 -3  sum = max_here + nums[2] which is:
sum = 1 + (-3) = - 2 which is bigger than nums[2] which is -3 so max_here = sum = -2But max_here is smaller than max_so_far so max_so_far stays equal to 1
-2 1
3 4  sum = -2 + 4 = 2 which is < than 4 so  max_here = nums[4] = 4 which is > max_so_far so max_so_far = max_here = 4 4 4
4 -1   sum = 4 - 1 = 3 > – 1 so
max_here = sum = 3
3 4
5 2  sum = 3 + 2 = 5
which is > than nums[5] = 2 so
max_here = max_so_far = sum = 5
5 5
6 1  sum = 5 + 1 = 6
which is > than nums[6] = 1 so
max_here = max_so_far = sum = 6
6 6
7 -5  sum = 6 + (-5) = 1
which is > than nums[7] = -5 so
max_here = 1
1 6
8 4  sum = 1 + 4 = 5
which is > than nums[8] = 4 so
max_here = 5 but
max_here < max_so_far so
max_so_far stays the same: 6 which is the maximum sum here.
5 6

 

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    var max_here = max_so_far = nums[0];
    

    for(var i=1;i < nums.length; i ++) {
        max_here = Math.max(max_here + nums[i], nums[i]);
        max_so_far = Math.max(max_so_far, max_here);
    }    

    return max_so_far;
};

 

 

Adding Jest and Enzyme and creating snapshot tests

branch-name:  
Click To Copy

 

In this tutorial we will learn how to install Jest and Enzyme, and create Snaphot tests.

If you are not familiar with these tools please read the links first since we will focus mainly on installing and using the tools.

The basic idea is that we are going to create a test cases, that will render the component, and create a snapshot of it. Next time when we run the test we could compare with the snapshot, making sure that either the component is not changed, or it renders as expected.

Setting up Jest and Enzyme.

We will need some extra packages in order to make Jest work with Enzyme.

Installing the packages.

  • enzyme – the actual enzyme testing utility.
  • enzyme-adapter-react-16 – the react adapter for enzyme. There are different adapters for different React versions, and since we are using react 16 we will install this version. A full list of adapter packages could be found here
  • enzyme-to-json – a serializer for the components.
yarn add jest enzyme enzyme-adapter-react-16 enzyme-to-json --dev

In addition we have to also install:

yarn add babel-jest --dev

  • babel-jest – allows Jest to use babel to transpile JS.

Before using Enzyme we have to configure it first. Create file with the following contents:

src/setupTests.js

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

This tells Enzyme, to run with React using the adaptor we just installed. Make sure this file is included before using Enzyme. Open package.json and add the following config:

./package.json

...
  },
  "jest": {
    "setupFiles": [
      "./src/setupTests.js"
    ]
  },
...

Now we are almost ready to write our first test. Header component is perfect candidate since it is not connected to the redux store.

Create new file in the ./src/components/Header folder and let’s

Add a Snapshot Test

./src/components/Header/index.test.js

import React from 'react';
import { shallow } from 'enzyme';
import Header from './index';
import toJson from 'enzyme-to-json';

describe('Testing Header component', () => {
    it('renders as expected!!!', () => {
      const wrapper = shallow(
        <Header title="Title test" />
      );
     expect(toJson(wrapper)).toMatchSnapshot();
   });
});

 unfortunately if we do yarn test it will fail with error code, since Jest doesn’t know how to deal with anything different than Java Script.
But there is an easy fix: we could map CSS to a JS file that Jest understands. Create empty file in

./src/__mocks__/styleMock.js


and set up Jest to use the mapping:

./package.json

...
  "jest": {
    "setupFiles": [
      "./src/setupTests.js"
    ],
    "moduleNameMapper": {
      "\\.(css|less|scss)$": "<rootDir>/src/__mocks__/styleMock.js"
    }    
  },
...

Now let’s run yarn test and we should see something like this:

yarn test
yarn run v1.12.3
$ jest
 PASS  src/components/Header/index.test.js
  Testing Home component
    ✓ renders as expected!!! (15ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 passed, 1 total
Time:        2.194s
Ran all test suites.
✨  Done in 3.05s.

Perfect ! Our first snapshot test is done.

Testing renderer component

src/components/Home/renderer.test.js

import React from 'react';
import { shallow, mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import Component from './renderer';

describe('Testing Home component', () => {
    it('renders as expected!!!', () => {
      const wrapper = shallow(<Component styles={{}} title="MyTest" />);

     expect(toJson(wrapper)).toMatchSnapshot();
     expect(wrapper.contains(<div>MyTest</div>)).toBe(true);
   });
});

Also we added expect(wrapper.contains(<div>MyTest</div>)).toBe(true); which looks for certain things into the component.

Testing Redux connected component.

./src/components/About/index.test.js

import React from 'react';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import { Provider } from 'react-redux';
import reducers from '../../reducers';
import { createStore} from 'redux';
import About from './index.js';

const store = createStore(reducers, {});
let wrapper;
describe('Testing About component', () => {

  beforeEach(() => {
    // Runs a function before each of the tests in this file runs
    wrapper = mount(
      <Provider store={store}>
        <About userName="Toni" />
      </Provider>
    );
  });

  it('renders as expected', () => {
   expect(toJson(wrapper)).toMatchSnapshot();
  });

  it('expect to have state.userName set to John', () => {   
   wrapper.setState({userName: "John"});
   expect(wrapper.state('userName')).toEqual("John");
  });  
});

since we wrapped MyComponent with Provider component, we have to mount the component that will render it in full instead of doing shallow rendering (like in the previous example). Otherwise shallow will render only the Provider component.

Now the two tests could be executed together yarn test

 PASS  src/components/About/index.test.js
  Testing About component
    ✓ renders as expected (56ms)
    ✓ expect to have state.userName set to John (14ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   1 passed, 1 total
Time:        3.102s
Ran all test suites matching /.\/node_modules\/jest\/bin\/jest.js|src\/components\/About\/index.test.js/i.

Adding more tests.

./src/components/Greetings/renderer.test.js

import React from 'react';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import reducers from '../../reducers';
import { createStore} from 'redux';
import Component from './renderer';

const store = createStore(reducers, {});
let wrapper;
describe('Testing Greetings component', () => {

  beforeEach(() => {
    wrapper = mount(
        <Component styles={{}} store={store} />
    );
  });

  it('renders as expected', () => {
    // to snapshot test
    expect(toJson(wrapper)).toMatchSnapshot();
  });

  it('textbox does not exist', () => {  
    expect(wrapper.find('input').exists()).toBe(false);
  });

  it('textbox exists after h2 click', () => {  
    // simulate click on the h2 tag trigering props change and visualizing the input text box
    wrapper.find('h2').simulate('click');
    expect(wrapper.find('input').exists()).toBe(true);
  });    

  it('textbox values change properly', () => {    
    // text box value tests
    expect(wrapper.find('input').props().value).toBe('No Name');
    wrapper.find('input').props().value = 'test';
    expect(wrapper.find('input').props().value).toBe('test');
  });    

});
  • beforeEach – Runs a function before each of the tests in this file runs. If the function returns a promise or is a generator, Jest waits for that promise to resolve before running the test.
  • here is another way of passing the store directly to the component.
branch-name:  
Click To Copy

Setting up production server.

branch-name:  
Click To Copy

 

Developing config is adding a lot of heavy processing in order to have source mapping working and debugging easy, but we don’t need this in production. All we need is speed. Let’s create another config that we are going to use for production build.

There are different ways to achieve this. One way for example is to create a common config and one for production and one for development. Then use webpack-merge plugin to combine the configs like it was explained here , or we could simply create another Webpack config for running the production server, but then we have to manage multiple configs which is far from perfect, so let’s use the same concept like before and extend (and override) the base config with production one.

Creating production config.

Add

./webpack.prod.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');

config.mode = "production";
config.devtool = "";

config.entry = [
    './src/index.js', 
];

module.exports = config;

also lets modify the base config to use plain JavaScript syntax since Webpack won’t know how to run it and at this point there is no Babel teanspiler to convert the code to plain JavaScript.

./webpack.base.config.js

const getEnvironmentConstants = require('./getEnvironmentConstants');
const webpack =require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',    
    './src/index.js',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist/',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },

      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      },
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      },
      //File loader used to load fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }                    
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } )
  ]
};

Creating production build.

Edit package.json and add clean script to remove the dist folder and build-prod script.

./package.json

...
"scripts": {
  "start-cli": "webpack-dev-server --hot --history-api-fallback --config webpack.cli.config.js",
  "start-api": "babel-node server-api.js",
  "start-middleware": "babel-node server-middleware.js",
  "clean": "rm -rf ./dist",
  "lint": "eslint .",
  "build-dev": "webpack --mode development",
  "build-prod": "webpack --config webpack.prod.config.js",
},
...

what we just did:
– we added a cleanup script clean that will clean up the production build directory (line 6)
– we added a production build script (line 9)

Now we could clear the ./dest folder by running yarn clean then do the production build yarn build-prod and have production bundle ready.

Creating Express production server.

There are different ways to serve your files once the production build is done. One way is to use nginx server which I might create a tutorial of how to set up later, or we could use the express server.

Let’s create an Express config and run a simple prod server. Create the prod.server.js with the following contents:

./prod.server.js

const express = require('express');
const path = require('path')
const app = express();
const port = 8080;

app.use('/dist', express.static('dist'));
app.get('*', (req, res)=>{
  res.sendFile(path.join(__dirname, 'index.html'));
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

 what we just did:
– we created an Express server
– we told the server to serve every static file like JS, CSS, images from the /dist folder (line 6)
– everything else that is not matching the static content falls into ‘*’ and will serve index.html

Looks familiar? It’s pretty similar to that we did in the previous chapters.

Let’s add a production server start script:

./package.json

"scripts": {
  "start-cli": "webpack-dev-server --hot --history-api-fallback --config webpack.cli.config.js",
  "start-api": "babel-node server-api.js",
  "start-middleware": "babel-node server-middleware.js",
  "clean": "rm -rf ./dist",
  "lint": "eslint .",
  "build-dev": "webpack --mode development",
  "build-prod": "webpack --config webpack.prod.config.js",
  "run-prod-server": "node ./prod.server.js",
  "build-and-run-prod-server": "yarn clean && yarn build-prod && yarn run-prod-server"
},

 what we just did:
– we created `run-prod-server` that will simply start the express server with will statically serve the contents of the /dist folder where build-prod script is dumping the bundle.
– To make things easier we also created a new script entry (line 10) that does all necessary steps to run production server:
– calls yarn clean script to remove the ./dist folder before rebuilding
– calls yarn build-prod to make a fresh production buid
– tells node to start using ./prod.server.js that we just created as an entry point, which starts the Express server.

Give it a try yarn run-prod-server and observe the ./dist folder. It will disappear for a brief moment, then it will re-appear with the fresh content and once you see this message in the console ‘example app listening on port 8080!’ you are ready to hit the browser url.

If everything went well, you should see the site running, using the production Express server.

Extracting CSS in a separate files.

Right now, if we look at the ./dist folder we will see only *.js and the font file that we added, which means that all CSS is embedded into Javascript files, which is ok for development, but far from ideal for production. Now the browser can’t cache the CSS, and we will see issues with styling the components since it will take time for the CSS to be applied. To solve this problem we have to extract the CSS from JS and load it separately.

Adding mini-css-extract-plugin.

And there is a plug-in for this called mini-css-extract-plugin. Let’s install it.

yarn add mini-css-extract-plugin --dev

and let’s tell Webpack to use it only with the production build:

./webpack.prod.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

config.mode = "production";
config.devtool = "";

config.entry = [
    './src/index.js', 
];

config.module.rules[1].use[0] = MiniCssExtractPlugin.loader;

config.plugins = [ ... [new MiniCssExtractPlugin({
        // these are optional
        filename: "[name].css",
        chunkFilename: "[id].css"
    })], 
    ... config.plugins
];

module.exports = config;

 what we just did:
– we replaced ‘style-loader’ with mini-css-extract-plugin to extract all CSS files form JS bundle (line 12)
– by default even without any parameters mini-css-extract-plugin will create two bundle types: one with the main.css and the rest per component base. Which means that each component will have their own CSS bundle, dynamically loaded when the component is rendered. For a large project this will dramatically reduce the initial CSS load.

You could play with the optional parameters: filename and chunkFilename and see how this will affect generated CSS files names. If for example you would like to rename the main file to global-main.css you could set filename: "global-[name].css" or if you want to change the chunk names you could do this: chunkFilename: "[id].css" . Parameters in the brackets are automatically replaced  by Miini-css-extract-plugin. [id] – will be replaced with the chunk id # for example.

Rebuild the project:

yarn build-prod

And let’s look at ./dist folder. Below every JS bundle we should be able to see the corresponding CSS for this module. Now it looks better. Run the production server build-and-run-prod-server load the project and observe the net tab while navigating in different site sections. You will see the css files loading for each component once you navigate to a new section. But we are missing the main.css which is not loading on demans. Let’s add a tag to load it:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Babel Webpack Boilerplate</title>
        <link rel="stylesheet" type="text/css" href="dist/main.css">
    </head>
    <body>
        <div id="root"></div>
        <script type="text/javascript" src="dist/main-bundle.js"></script>
    </body>
</html>

Run the production server again and the app should look like before.

But there is another problem: click to see the file content’s and you will notice that the CSS is not minified.

Minimizing the CSS.

Let’s use optimize-css-assets-webpack-plugin

yarn add optimize-css-assets-webpack-plugin --dev

./webpack.prod.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

config.mode = "production";
config.devtool = "";

config.entry = [
    './src/index.js', 
];

config.module.rules[1].use[0] = MiniCssExtractPlugin.loader;

config.plugins = [ ... [new MiniCssExtractPlugin({
        // these are optional
        filename: "[name].css",
        chunkFilename: "[id].css"
    })],
    new OptimizeCSSAssetsPlugin({}),   
    ... config.plugins
];

module.exports = config;

Rebuild production and run the prod server and if you open some of the CSS file contents you will see that they are now minified.

Just what we need for production build!

branch-name:  
Click To Copy

 

 

Adding environment variables file.

branch-name:  
Click To Copy

 

Although Wepack already comes with two modes: development and production, It comes handy to have different environment variables for development and production and to store them in different files.

Let’s create .env file which will be our development environment variable file.

Creating the .env file

./.env

APP_NAME=Webpack React Tutorial
GRAPHQL_URL=http://localhost:4001/graphql

As it looks like .env is not a standart JavaScriupt object so we can’t use the variables out of the box. We will need two modules Dotenv and Dotenv-expand.

Adding and configuring Dotenv to load variables into process.env.

Dotenv is module that loads environment variables from a .env file into process.env.

yarn add dotenv dotenv-expand

Let’s give it a try and print out GRAPHQL_URL in the backend. Let’s do this using server-api.js config. All that we need to do is to ‘tell’ Dotenv module to load variables into process.env

./src/server-api.js

import WebpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';
import config from './webpack.api.config.js';
require('dotenv').config();

console.log(">>>" + process.env.GRAPHQL_URL);

const compiler = webpack(config);
const server = new WebpackDevServer(compiler, {
  hot: true,
  publicPath: config.output.publicPath,
  historyApiFallback: true
});
server.listen(8080, 'localhost', function() {});

Run the project and in the backend console you will see GraphQL url printed out.

Now, let’s create getEnvironmentConstants helper method that will use Dotenv to load variables, and in addition we will add a filter that will load only these variables to the front end that we specify in frontendConstants . This way important variables that we need in the backend like passwords to the database won’t be exposed in the source code.

./getEnvironmentConstants.js

const fs = require('fs');

// Load environment variables from these files
const dotenvFiles = [
  '.env'
];


// expose environment variables to the frontend
const frontendConstants = [
  'APP_NAME',
  'GRAPHQL_URL'
];

function getEnvironmentConstants() {
  
  dotenvFiles.forEach(dotenvFile => {
    if (fs.existsSync(dotenvFile)) {
      require('dotenv-expand')(
        require('dotenv').config({
          path: dotenvFile,
        })
      );
    }
  });
  
  const arrayToObject = (array) =>
  array.reduce((obj, item, key) => {
    obj[item] = JSON.stringify(process.env[item]);
    return obj
  }, {})

  return arrayToObject(frontendConstants);      
}

module.exports = getEnvironmentConstants;

Once we have the object in place we could use the DefinePlugin to pass them to the frontend.

Adding the DefinePlugin.

if we go back in this tutorial we will remember that we showed how to configure Webpack in three different ways: using CLI, the webpack API and the server middleware.
Now the best place to add DefinePlugin will be in webpack.base.config so all three Webpack set-ups will take advantage of it. Let’s import getEnvironmentConstants and pass it as a parameter to DefinePlugin.

./webpack.base.config.js

import getEnvironmentConstants from './getEnvironmentConstants';
import webpack from 'webpack';

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',    
    './src/index.js',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },

      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      },
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      },
      //File loader used to load fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }                    
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } )
  ]
};

 what we just did:
– (line 1 and 2) we imported getEnvironmentConstants and Webpack since we will need it to instantiate the plug in.
– (line 72-74) we added the plug in.

We have to do one more change in order to have the plug-in working for all Webpack configs:

./webpack.api.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');

config.entry = [
  '@babel/polyfill',    
  './src/index.js',
  'webpack/hot/dev-server',
  'webpack-dev-server/client?http://localhost:8080/',       
];

config.plugins = [... [new webpack.HotModuleReplacementPlugin()], ... config.plugins ];

module.exports = config;

and

./webpack.middleware.config

const webpack = require('webpack');
let config = require('./webpack.base.config.js');

config.entry = [
  '@babel/polyfill',    
  './src/index.js',
  'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',        
];

config.plugins = [... [new webpack.HotModuleReplacementPlugin()], ... config.plugins ];

module.exports = config;

 what we just did:
Since we added config.plugins array in webpack.base.config we don’t want to override it here and lose the changes. That’s why we are merging the array using spread operator.
If you are unfamiliar with the spread operator you might read the link above. Basically what it does is to ‘spread’ two or more arrays into the current array.

var a = [1,2,3];

var b = [...a, ...[4,5,6]];

console.log(b);

result: (6) [1, 2, 3, 4, 5, 6]

Accessing env variables in the front-end.

And let’s load these variables. We could replace the hardcoded GraphQL url with the one from the .env file.

And using the variables on the back end is straight forward: we just include .env file and use the variables, but passing them to the front end requires a little bit more effort. We have to use the DefinePlugin which will allow us to create global constants which can be configured at compile time.

./src/components/App/index.js

import React, { Component } from 'react';
import PageLayout from '../../containers/PageLayout';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore} from 'redux';
import reducers from '../../reducers';

import styles from './styles.scss';

const store = createStore(reducers, {});
export default class App extends Component {
  render() {
    const GRAPHQL_URL = process.env.GRAPHQL_URL;
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL }),
      cache: new InMemoryCache()
    });  
    return (
      <div className={styles.appWrapper}>
        <Provider store={store}>
          <ApolloProvider client={client}>
            <Router>
              <Switch>
              <Route exact path="*" component={PageLayout} />  
              </Switch>
            </Router>
          </ApolloProvider>
        </Provider>
      </div>        
    );
  }
}

 

And we could also print the APP name in the header section process.env.APP_NAME

./src/components/Header/index.js

import React from 'react';
import { Link } from 'react-router-dom';
const styles = require('./styles.scss');
const Header = ( {title} ) => (
  <div>
    <div className={styles.wrapper}>      
      <h2>{ title } { process.env.APP_NAME } </h2>
      <ul>
        <li><Link to='/home'>HOME</Link></li>
        <li><Link to='/greetings'>GREETINGS</Link></li>       
        <li><Link to='/dogs-catalog'>DOGS CATALOG</Link></li>
        <li><Link to='/about'>ABOUT</Link></li>
      </ul>
    </div>
  </div>
);
export default Header;

Now, start the server using yarn start-api and if everything works fine you will see the “Webpack React Tutorial” in the header.

 

branch-name:  
Click To Copy

 

 

Breaking the schema into separate group of files.

Having the entire schema into the server.js file is not practical, especially if the schema grows bigger, so let’s move the schema into separate folders for easy management:

mkdir ./src/schema/queries -p
mkdir ./src/schema/types -p
mkdir ./src/schema/mutations -p

./src/schema/index.js

const graphql = require('graphql');
const queries = require('./queries');
const mutations = require('./mutations');

var rootQuery = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {...queries, ...mutations },
});

module.exports = new graphql.GraphQLSchema({
  query: rootQuery
});

./src/schema/types/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs,
};

./src/schema/types/dogs.js

const graphql = require('graphql');

module.exports = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

./src/schema/queries/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs
};

./src/schema/queries/dogs.js

const graphql = require('graphql');
const dogType = require('../types/dogs');
const dogs = require('../../models/mock_data/dogs.js');

module.exports = {
  getDogByBreed: {
    type: dogType,
    args: {
      breed: { type: graphql.GraphQLString }
    },
    resolve: function (_, {breed}) {
      var result = dogs.find(function(dog){
        return breed == dog.breed;
      });
      return result;
    }
  } 
}

./src/schema/mutations/index.js

let Dogs = require('./dogs.js');

module.exports = {
  ...Dogs
};

./src/schema/mutations/dogs.js

const graphql = require('graphql');
const dogs = require('../../models/mock_data/dogs.js');

module.exports = {
  addDogBreed: {
    type: graphql.GraphQLString,
    args: {
      id: { type: graphql.GraphQLString },
      breed: { type: graphql.GraphQLString },
      displayImage: { type: graphql.GraphQLString }
    },
    resolve: function (_, {id, breed, displayImage}) {
      dogs.push({
        id: id,
        breed: breed,
        displayImage: displayImage
      });
      return "OK!";
    }
  }   
}

./server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
const schema = require('./src/schema');


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 

Setting up faster development environment using Nodemon and Babel.

Adding Nodemon

Nodemon is the same like node with the benefit that it will monitor the developing folder and restart automatically,, which makes the developing process more convenient. Let’s go ahead and install it:

yarn add nodemon --dev

And replace the node with nodemon in the start script:

./package.json

{
  "name": "graphql-tutorial",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "nodemon server.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.7.1",
    "graphql": "^14.0.2"
  },
  "devDependencies": {
    "nodemon": "^1.18.9"
  }
}

Now, whenever we edit and save a file Nodemon will conveniently restart for us and reflect the new changes.

If we also want to get advantage of the latest ES syntax we would like to install Babel.

 

Adding Babel 7

Starting with Babel7 the config setting changes quite a bit.

  1. Babels packages are now scoped and Babel has renamed it’s NPM packages. This means babel-cli for example has been renamed to @babel/cli .
  2. No messing around with presets anymore. You can just use @babel/preset-env now and optionally define your requirements in the config file.
  3. babel-node has been moved from the CLI to it’s own package: @babel/node

Let’s go ahead and install all necessary packages:

yarn add @babel/core @babel/cli @babel/node --dev

and tell nodemon to use Babel to transpile JS.

./package.json

{
  "name": "graphql-tutorial",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "nodemon --inspect=10111 --exec babel-node server.js"
  },
  "dependencies": {
    "express": "^4.16.4",
    "express-graphql": "^0.7.1",
    "graphql": "^14.0.2"
  },
  "devDependencies": {
    "nodemon": "^1.18.9"
  }
}

 

Before we could start using the new ES features we have to install a preset and tell Babel to use it by adding ./babelrc config file

yarn add @babel/preset-env --dev

add

./.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry"
      }
    ]
  ]
}

 

give it a try …

./server.js

import express from 'express';
import graphqlHTTP from 'express-graphql';
import schema from './src/schema';


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 

Using GraphQLSchema to construct the schema programmatically.

For most of the cases defining a fixed schema when the application starts, by adding Query and Mutation types solely using schema language is good enough. But sometimes we might need to define a dynamic schema and we can achieve this by creating a new JS objects.

Construct dynamic schema for ‘User Greeting’ example.

Let’s get again to the ‘Greetings user’ example, because of it’s simplicity and define a Query with field named greetingUser which will accept userName and bornMonth parameters, first of type string and the second of type int and return userType.

And the userType will return greetingOne  which will simply say “Hello [userName]” and greetingTwo  that will let the user know how many months left till their next birthday. Both of type string.

./src/server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the User type
var userType = new graphql.GraphQLObjectType({
  name: 'UserType',
  fields: {
    greetingOne: { type: graphql.GraphQLString },
    greetingTwo: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    greetingUser: {
      type: userType,
      args: {
        userName: { type: graphql.GraphQLString },
        bornMonth: { type: graphql.GraphQLInt }
      },
      resolve: function (_, {userName, bornMonth}) {
        var date = new Date();
        var daysLeft = bornMonth - (date.getMonth() + 1);
        daysLeft = daysLeft < 0 ? daysLeft + 12 : daysLeft;
        return {
          greetingOne: `Hello ${userName}`,
          greetingTwo: `Your birthday is comming in ${daysLeft} month(s)`
        };
      }
    }
  }
});

var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

what we just did:
– we defined the user type which is pretty self explanatory (Lines 8-14)
– then we created the query type, that has these parameters:
type which is the return type, in this case userType
args is the input parameter types.
resolve is the resolver function.

Transform Dogs catalog to use dynamic schema.

Creating the dog type

var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

Creating the query type

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    }
  }
});

Creating a mutation type

...
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    } 
...

Adding the query schema

...
var schema = new graphql.GraphQLSchema({query: queryType});

...
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
...

Putting it all together

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the dogs type
var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    },
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    }    
  }
});


var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

GraphQL schemas, queries and resolvers.

In the previous example we learned how to run GraphQL with Express http server and we created a simple query example with returns hardcoded data. Let’s make this useful and create real world example.

Adding Nodemon for rapid development.

Before we continue, let’s make development process easier and run the project using Nodemon instead of Node. Nodemon is the same like Node but it also monitors the project folder for changes, and automatically restarts the server for us.

yarn add nodemon --dev

Creating schema and resolvers.

Technically speaking we already created our schema and resolver in the previous example. Let’s quickly go through what we did:

The most basic way to create a response in GraphQL is like it was described in the previous tutorial: creating schema describing the field name and type i.e.

var schema = buildSchema(`
  type Query {
    hello: String
  }
`);

and then creating a resolver i.e.

var root = {
  hello: () => {
    return 'Hello world!';
  },
};

which simply returns whatever is in the return statement: in this case “hello world!” for any hello field request.

Let’s make the example more useful by

Passing parameters.

The basic types that we could use out of the box are StringIntFloatBoolean, and ID

Let’s change the hello query to accept userName parameter of type String and prints ‘hello [userName]’. Also we will add another field called rollDice and will return a random number between 0 and 10.
Passing parameters requires two steps:
– define the parameter in the schema (line 8) hello(userName:String!): String
– read the parameter in the resolver (line 15) hello: (args) => { ...

./server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
  type Query {
    hello(userName:String!): String,
    rollDice: Int
  }
`);
// The root provides a resolver function for each API endpoint

var root = {
  hello: (args) => {
    return `Hello ${args.userName}`;
  },
  rollDice: () => {
    return Math.floor(Math.random() * 6) + 1;
  }
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));

app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

 what we just did:
– we added userName parameter in the schema and defined it as s String type (line 8)
– in the resolver we are getting the userName parameter from the args parameter, and returning the greeting line 16.
– in addition we also added another field call rollDice which returns a random number between 0 and 10.

Making queries with parameters.

Save the file and navigate the browser to http://localhost:4000/graphql

GraphQL UI

 

Explore the schema using documentation explorer.

Look at the top right side in the browser (picture above) and you will find the Documentation explorer (If it is collapsed click on ‘Docs’ in the upper right section). Let’s pretend that we didn’t build the schema but we still need to know what queries we could fire. That’s where Documentation explorer could help.
Click on Query and you will see this:

hello(userNameString!): String
This describes the ‘hello’ and the ‘rollDice’ queries, the input parameters and the output type.

Making the query.

Since now we know the query name and the parameters and their types we are ready to write our first query with parameters.

Add the query in the up left text box like it’s shown on the picture above,

query queryLabel($userName: String!) {
  hello(userName: $userName)
}

and then add the parameter in the left, down box named query variables. When we write query with parameters it’s always better instead to directly pass the parameter value, to use the $ syntax to define a variable in the query.

{
  "userName": "Sam Smith"
}

 what we just did:
– we described a query with label queryLabel and input parameter called $userName of type String
– The ! symbol means that $userName can’t be null
– (line 2) we are querying hello and passing $userName as a parameter
– in the second code snipped (under query variables) we are passing the actual value of our parameters. In our case the value of $userName

Hit the play button in the upper left section and you will get the response:

{
  "data": {
    "hello": "Hello Sam Smith"
  }
}

If we also want to query the rollDice we could just add this to our query:

query queryLabel($userName: String!) {
  hello(userName: $userName)
  rollDice
}

The great thing about GraphQL is that it will return only the queries that we requested, and also we could combine multiple queries (from the same schema) in one response.

Adding Redux

branch-name:  
Click To Copy

 

In the previous tutorial we showed how to build custom Redux like store, which is a great way to understand how Redux work, but if we want a production grade app, we have to do more work on our custom Redux like store, since we cheated a bit.

For example our redux store emits setState no matter if the properties are actually changed or not which is ok for a demo app but for a big app with a lot of mapStateToProps it will have performance impact, so let’s not re-invent the wheel but use the Redux store instead.

yarn add redux react-redux

Since Redux like implementation that we did in the previous tutorial is very similar to the actual Redux, there are just a few changes that we need to do in order to start using Redux.

./src/components/App/index.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import PageLayout from '../../containers/PageLayout';
import { Provider } from 'react-redux';
import { createStore} from 'redux';
import reducers from '../../reducers';

import fetch from 'unfetch';


const store = createStore(reducers, {});

export default class App extends Component {

  render() {

    const GRAPHQL_URL = 'http://localhost:4001/graphql';
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL, fetch: fetch }),
      cache: new InMemoryCache()
    });  

    return (
      <Provider store={store}>
        <ApolloProvider client={client}>
          <Router>
            <Switch>
              <Route exact path="*" component={PageLayout} />    
            </Switch>
          </Router>
        </ApolloProvider>
      </Provider>
    );
  }
}

 what we just did:
– we removed our custom store implementation:

import Store from ‘../../store’;
import Reducers from ‘../../reducers’;
import Provider from ‘../../containers/Provider’;


– and replaced it with the Redux provider and create store (line 8 and 9)
– we called the Redux createStore (line 15)
– finally we wrapped the app with the new provider passing the store (line 28)

Next step is just cleaning up the code a bit.
– We renamed the index.js reducer to ./src/reducers/useer.js so we could keep each different reducer types in separate files and we added one main reducer ./src/reducers/index.js that will combine all other reducers. (in our case ./src/reducers/useer.js for now)

./src/reducers/user.js

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

const initialState = {
  todos: [],
  userName: "No Name",
  editMode: false
};

  const reducer = (state = initialState, action) => {
    switch (action.type) {
      case CHANGE_USERNAME: {
        let newState = {...state};
        newState.userName = action.data;
        return newState;
      }

      case TOGGLE_EDIT_MODE: {
        let newState = {...state};
        newState.editMode = !newState.editMode;
        return newState; 
      }

      default:
        return state;
    }
  };

export default reducer;

 

./src/reducers/index.js

import { combineReducers } from 'redux';
import user from './user';

export default combineReducers({
  user
});

 

And the best part is that since our Redux like implementation was so similar to the actual Redux implementation, we have to make just two little changes:
– Replace import connect from '../../containers/Provider/connect'; with the Redux connect import { connect } from 'react-redux';
– And since we added combineReducers we have to add user property to access the users reducer.

./src/components/About/index.js ./src/components/Greetings/index.js.

import React, { Component } from 'react';
import { connect } from 'react-redux';

const styles = require('./styles.scss');

const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

class Greetings extends Component {

  constructor(props) {
    super(props); 
  }
  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.props.changeUserName(newName);
    this.props.toggleLogInPopup();
  }

  usernameChanged(el) {
    let newName = el.target.value;    
    this.props.changeUserName(newName);
  }

  onToggleEditMode() {
    this.props.toggleLogInPopup();
  }

  render() {
    let element = <h2 onClick={() =>{   this.onToggleEditMode()  }}>Hello:  {this.props.userName}</h2>;
    if(this.props.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.props.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
      <div>
        <div className={styles.wrapper}>
          {element}
        </div>
      </div>);
  }
}

const mapStateToProps = ( storeState ) => {
  return {  
    userName: storeState.user.userName,
    editMode: storeState.user.editMode
  }
}

const mapDispatchToProps = dispatch => {
  return {
    toggleLogInPopup: () => {
      dispatch({type: TOGGLE_EDIT_MODE});
    },
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Greetings);

 

./src/components/About/index.js ./src/components/About/index.js

import React, { Component } from 'react';
import { connect } from 'react-redux';

const CHANGE_USERNAME = 'CHANGE_USERNAME';
class About extends Component {
  constructor(props) {
    super(props);
    this.state = {
      userName: this.props.userName,
    };    
  }
  handleChange() {
    const userName = document.querySelector('input[name=username]').value;
    this.setState( { userName: userName } );
    this.props.onEdit(userName);
  }
  render() {
    return (
      <div>
        <p>This is <input type="text" name="username" value={this.state.userName} onChange={() => { this.handleChange()}} /></p>
      </div>
    );
  }
}
//export default About;
const mapStateToProps = storeState => ({
  userName: storeState.user.userName
}
);
const mapDispatchToProps = dispatch => ({
  onEdit: (userName) => dispatch({
    type: CHANGE_USERNAME,
    data: userName
  })
});
const AboutContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(About);
export default AboutContainer;

 

And delete ./src/containers/Provider folder.

And now all custom Redux like implementation was replaced wit the actual Redux store. Give it a try and everything shoul;d work like before but using the actual Redux store.

 

branch-name:  
Click To Copy