Welcome to the 4th and final part of my full stack app development series with Spring Boot, Spring Security, JWT, MySQL and React.

In the last 3 articles, we created the backend project, setup spring security with JWT authentication, and written the rest APIs for Login, Signup, Polls, and Users.

In this article, We’ll build the client side of the application using React and Ant Design.

You can find the complete source code for the project on Github. The project is also hosted on AWS free tier for you to explore. Check out the live demo of at https://polls.callicoder.com.

Spring Boot, Spring Security, JWT, MySQL, React, Ant Design Full Stack Polling App with User Registration and Authentication Part 4

Bootstrapping the Front End Project

Follow the steps below to setup the front-end project.

1. Installing create-react-app

First of all, we’ll install create-react-app, a command line tool for creating react apps

npm install -g create-react-app

2. Creating the app

Now let’s create the app using create-react-app tool by typing the following command -

create-react-app polling-app-client

3. Installing Additional Dependencies

We’ll be using the following additional dependencies in our project -

  1. And Design: An excellent react based user interface library for designing the user interface.

  2. React Router: Client side routing solution for react apps.

Let’s install these dependencies by typing the following command

cd polling-app-client
npm install antd react-router-dom --save

We’ll also need some dev dependencies to customize Ant Design’s theme and enable on-demand component import. Type the following command to install these dev dependencies -

npm install react-app-rewired babel-plugin-import react-app-rewire-less --save-dev

4. Configuring Ant Design

Now let’s configure ant design and customize its theme by overriding less variables.

  • Using react-app-rewired to customize default webpack config

    We’ll use react-app-rewired to enable customization. Open package.json file and replace the following scripts

    "scripts": {
      "start": "react-scripts start",
      "build": "react-scripts build",
      "test": "react-scripts test --env=jsdom",
      "eject": "react-scripts eject"
    }
    

    with these scripts -

    "scripts": {
      "start": "react-app-rewired start",
      "build": "react-app-rewired build",
      "test": "react-app-rewired test --env=jsdom",
      "eject": "react-scripts eject"
    }
    
  • Overriding configurations with config-overrides.js

    Now create a file named config-overrides.js in the root directory of the project and add the following code to it -

    const { injectBabelPlugin } = require('react-app-rewired');
    const rewireLess = require('react-app-rewire-less');
    
    module.exports = function override(config, env) {
        config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);
        config = rewireLess.withLoaderOptions({
          modifyVars: {
              "@layout-body-background": "#FFFFFF",
              "@layout-header-background": "#FFFFFF",
              "@layout-footer-background": "#FFFFFF"
          },
          javascriptEnabled: true
        })(config, env);
        return config;
    };
    

    Notice how we’re overriding Ant Design’s default less variables to customize the theme as per our needs.

5. Running the App

We’re done with all the configurations. Let’s run the app by typing the following command -

npm start

Exploring the directory structure of the Project

Following is the directory structure of the complete front-end project. You can check out the entire source code on Github.

polling-app-client
  ↳ public
        ↳ favicon.png
        ↳ index.html
        ↳ manifest.json
  ↳ src
     ↳ app
         ↳ App.css
         ↳ App.js
     ↳ common
         ↳ AppHeader.css
         ↳ AppHeader.js
         ↳ LoadingIndicator.js
         ↳ NotFound.css
         ↳ NotFound.js
         ↳ PrivateRoute.js
         ↳ ServerError.css
         ↳ ServerError.js
     ↳ constants
         ↳ index.js
     ↳ poll
         ↳ NewPoll.css
         ↳ NewPoll.js
         ↳ Poll.css
         ↳ Poll.js
         ↳ PollList.css
         ↳ PollList.js    
     ↳ user
         ↳ login
             ↳ Login.css
             ↳ Login.js
         ↳ profile
             ↳ Profile.css
             ↳ Profile.js
         ↳ signup
             ↳ Signup.css
             ↳ Signup.js
     ↳ util
         ↳ APIUtils.js
         ↳ Colors.js
         ↳ Helpers.js
     ↳ index.css
     ↳ index.js
     ↳ logo.svg
     ↳ poll.svg
     ↳ registerServiceWorker.js                              
  ↳ config-overrides.js
  ↳ package.json   

In this article, I’ll give you a brief idea of how the project is structured and what each directory and files do.

Going through every piece of code in this blog will be very time consuming, and is absolutely unnecessary. The code is simple and self-explanatory. A basic knowledge of React is needed to understand the code.

You can download the code from Github and go through it. I’ll always be here to help if you get stuck at something.

All right! Let’s now understand some of the important pieces of code in our front-end project.

Understanding the front-end code

index.js

This file is the main entry point of our react application -

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './app/App';
import registerServiceWorker from './registerServiceWorker';
import { BrowserRouter as Router } from 'react-router-dom';

ReactDOM.render(
    <Router>
        <App />
    </Router>, 
    document.getElementById('root')
);

registerServiceWorker();

In the above script, we simply render the App component in an html element with id root (This html element is available in public/index.html file).

src/app/App.js

It defines the App component. The App component is the main top-level component of our application. It defines the primary layout and routing, loads the currently logged in user, and passes the currentUser and isAuthenticated property to other components.

import React, { Component } from 'react';
import './App.css';
import {
  Route,
  withRouter,
  Switch
} from 'react-router-dom';

import { getCurrentUser } from '../util/APIUtils';
import { ACCESS_TOKEN } from '../constants';

import PollList from '../poll/PollList';
import NewPoll from '../poll/NewPoll';
import Login from '../user/login/Login';
import Signup from '../user/signup/Signup';
import Profile from '../user/profile/Profile';
import AppHeader from '../common/AppHeader';
import NotFound from '../common/NotFound';
import LoadingIndicator from '../common/LoadingIndicator';
import PrivateRoute from '../common/PrivateRoute';

import { Layout, notification } from 'antd';
const { Content } = Layout;

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentUser: null,
      isAuthenticated: false,
      isLoading: false
    }
    this.handleLogout = this.handleLogout.bind(this);
    this.loadCurrentUser = this.loadCurrentUser.bind(this);
    this.handleLogin = this.handleLogin.bind(this);

    notification.config({
      placement: 'topRight',
      top: 70,
      duration: 3,
    });    
  }

  loadCurrentUser() {
    this.setState({
      isLoading: true
    });
    getCurrentUser()
    .then(response => {
      this.setState({
        currentUser: response,
        isAuthenticated: true,
        isLoading: false
      });
    }).catch(error => {
      this.setState({
        isLoading: false
      });  
    });
  }

  componentWillMount() {
    this.loadCurrentUser();
  }

  // Handle Logout, Set currentUser and isAuthenticated state which will be passed to other components
  handleLogout(redirectTo="/", notificationType="success", description="You're successfully logged out.") {
    localStorage.removeItem(ACCESS_TOKEN);

    this.setState({
      currentUser: null,
      isAuthenticated: false
    });

    this.props.history.push(redirectTo);
    
    notification[notificationType]({
      message: 'Polling App',
      description: description,
    });
  }

  /* 
   This method is called by the Login component after successful login 
   so that we can load the logged-in user details and set the currentUser &
   isAuthenticated state, which other components will use to render their JSX
  */
  handleLogin() {
    notification.success({
      message: 'Polling App',
      description: "You're successfully logged in.",
    });
    this.loadCurrentUser();
    this.props.history.push("/");
  }

  render() {
    if(this.state.isLoading) {
      return <LoadingIndicator />
    }
    return (
        <Layout className="app-container">
          <AppHeader isAuthenticated={this.state.isAuthenticated} 
            currentUser={this.state.currentUser} 
            onLogout={this.handleLogout} />

          <Content className="app-content">
            <div className="container">
              <Switch>      
                <Route exact path="/" 
                  render={(props) => <PollList isAuthenticated={this.state.isAuthenticated} 
                      currentUser={this.state.currentUser} handleLogout={this.handleLogout} {...props} />}>
                </Route>
                <Route path="/login" 
                  render={(props) => <Login onLogin={this.handleLogin} {...props} />}></Route>
                <Route path="/signup" component={Signup}></Route>
                <Route path="/users/:username" 
                  render={(props) => <Profile isAuthenticated={this.state.isAuthenticated} currentUser={this.state.currentUser} {...props}  />}>
                </Route>
                <PrivateRoute authenticated={this.state.isAuthenticated} path="/poll/new" component={NewPoll} handleLogout={this.handleLogout}></PrivateRoute>
                <Route component={NotFound}></Route>
              </Switch>
            </div>
          </Content>
        </Layout>
    );
  }
}

export default withRouter(App);

src/common - Common Components

  • AppHeader.js: Header component which renders Login & SignUp buttons for unauthenticated users, and Home, Profile & Create Poll buttons for authenticated users.

  • LoadingIndicator.js: It is used by other components to render a loading indicator while an API call is in progress.

  • NotFound.js: We use this in App component to render a 404 Not Found page if none of the routes match the current url.

  • PrivateRoute.js: A meta component that redirects to /login if the user is trying to access a protected page without authentication.

  • ServerError.js: Other components use this to render a 500 Server Error page if any API responds with a 500 error which the component can’t handle.

src/constants

I’ve defined all the global constants in src/constants/index.js file for other components use -

export const API_BASE_URL = 'http://localhost:5000';
export const ACCESS_TOKEN = 'accessToken';

export const POLL_LIST_SIZE = 30;
export const MAX_CHOICES = 6;
export const POLL_QUESTION_MAX_LENGTH = 140;
export const POLL_CHOICE_MAX_LENGTH = 40;

export const NAME_MIN_LENGTH = 4;
export const NAME_MAX_LENGTH = 40;

export const USERNAME_MIN_LENGTH = 3;
export const USERNAME_MAX_LENGTH = 15;

export const EMAIL_MAX_LENGTH = 40;

export const PASSWORD_MIN_LENGTH = 6;
export const PASSWORD_MAX_LENGTH = 20;

src/poll

  • NewPoll.js: Renders the Poll creation form.

  • PollList.js: This component is used to render a list of polls. It is used to render all polls on the home page. We also use this in user’s profile page to render the list of polls created by that user, and the list of polls in which that user has voted.

  • Poll.js: It is used by the PollList component to render a single Poll.

src/user

  • login/Login.js: The Login component renders the Login form calls the login API to authenticate a user.

  • signup/Signup.js: It renders the registration form and contains a bunch of client-side validations. It’s an interesting component to check out if you want to learn how to do form validations in React.

  • profile/Profile.js: The profile page renders a user’s public profile. It displays user’s basic information, the list of polls that the user has created, and the list of polls in which the user has voted.

src/util

  • APIUtils.js: All the Rest API calls are written in this script. It uses the fetch API to make requests to the backend server.

  • Colors.js: This util is used to get a random color to use in user’s avatar.

  • Helpers.js: It contains helper functions to format dates.

Over to you

I always prefer writing practical end-to-end tutorials on this blog. Full stack end-to-end tutorials like this one give you a broader picture of the application and lets you think through the challenges that are faced in all the stacks.

I hope that I didn’t overwhelm you with lots of code, and you were able to follow along and learn the concepts.

If something is unclear and you want me to explain it in detail, then let me know in the comment section below. I’ll do my best to explain it to you.

At last, I have some homework for you :) There are still a lot of stuff that we can improve in our polls app. I want you to work on them and submit a pull request on Github.

So here are some of the improvements that will make the polls app more awesome -

  • Email verification: There is no email verification right now in our application. Anyone can register with a random email. Write the logic to send an email verification mail to newly registered users with a verification link and verify user’s email once he clicks on the link.

  • Edit Profile: Add an edit profile page where a user can change his name, username, email, and password.

  • Forgot Password: Add forgot password functionality in the app.

I encourage you to implement the above functionalities. You will find many articles on the internet explaining how to implement things like email verification, forgot password etc. If you don’t find a proper answer on the internet, then write to me in the comment section below or send me an email. I’ll help you out.