diff --git a/.env-model b/.env-model
new file mode 100644
index 0000000000000000000000000000000000000000..6f4241fff681635d57ba88ec016f50949b1cf92c
--- /dev/null
+++ b/.env-model
@@ -0,0 +1,11 @@
+MYSQL_USER=localhost
+MYSQL_PASSWORD=root
+MYSQL_DATABASE=student
+MYSQL_HOST=localhost
+MYSQL_ROOT_PASSWORD=root
+MYSQL_HOST_IP=mysql
+
+REACT_APP_SERVER_PORT=8000
+
+NODE_PATH=src
+PMA_HOST=mysql
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b901d1fc371116bf65b24743d88845c679e40595
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/client/node_modules
+/server/node_modules
+.env
\ No newline at end of file
diff --git a/README.md b/README.md
index 7a3135480539c027be66ff7f5fe35581fc9b91b9..1a1a970204bec8efb58dce9cb4171d158be66185 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,27 @@
-# STUDENT
+# APPLICATION - STUDENT
 
-Front Student Module for ERP Project 2021
+## Front Student Module pour Projet ERP 2021
 
-To Install Project :
+#### Avant d'executer l'application :
 
-	in client/ and server/ run 	npm start
-	sudo npm install -g nodemon
+	Renommer le fichier .env-model en .env
 
-To Start Project :
+#### Executer l'application :
 
-	in client/ run 	npm start
-	in server/ run	nodemon
+	docker-compose up
+
+
+#### Une fois le build terminé et les services lancés :
+
+Le client (l'interface) :
+
+	localhost:3000
+
+
+Le serveur (API) :
+
+	localhost:8000/api
+
+Visualisation de base de données via PhpMyAdmin
+
+	localhost:8080
diff --git a/client/.dockerignore b/client/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..b7dab5e9cbfea4afa2bc788899ccaea6df849062
--- /dev/null
+++ b/client/.dockerignore
@@ -0,0 +1,2 @@
+node_modules
+build
\ No newline at end of file
diff --git a/client/Dockerfile b/client/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..e282c3f106a2c2dc5cea0929bad6c0f20df67b26
--- /dev/null
+++ b/client/Dockerfile
@@ -0,0 +1,10 @@
+FROM node:10-alpine
+
+RUN mkdir -p /app
+WORKDIR /app
+
+COPY . .
+
+RUN npm install
+
+CMD ["npm", "start"]
\ No newline at end of file
diff --git a/client/package.json b/client/package.json
index ba59707aa217ea3a41f40004459fe8e3eee24c1b..a4729a20057a287e633237d15b7adf70c3e86700 100644
--- a/client/package.json
+++ b/client/package.json
@@ -2,15 +2,21 @@
   "name": "client",
   "version": "0.1.0",
   "private": true,
+  "proxy": "http://server:8000",
   "dependencies": {
-    "@testing-library/jest-dom": "^5.14.1",
-    "@testing-library/react": "^11.2.7",
-    "@testing-library/user-event": "^12.8.3",
-    "react": "^17.0.2",
-    "react-dom": "^17.0.2",
-    "react-scripts": "4.0.3",
-    "web-vitals": "^1.1.2",
-    "bootstrap": "3.3.7"
+    "axios": "^0.19.0",
+    "bootstrap": "^5.1.1",
+    "cors": "^2.8.5",
+    "react": "^16.8.6",
+    "react-dom": "^16.8.6",
+    "react-redux": "^7.2.1",
+    "react-router-dom": "^5.2.0",
+    "react-scripts": "3.0.1",
+    "react-validation": "^3.0.7",
+    "redux": "^4.1.1",
+    "redux-thunk": "^2.3.0",
+    "validator": "^13.6.0",
+    "webpack": "4.29.6"
   },
   "scripts": {
     "start": "react-scripts start",
@@ -19,10 +25,7 @@
     "eject": "react-scripts eject"
   },
   "eslintConfig": {
-    "extends": [
-      "react-app",
-      "react-app/jest"
-    ]
+    "extends": "react-app"
   },
   "browserslist": {
     "production": [
@@ -36,5 +39,7 @@
       "last 1 safari version"
     ]
   },
-  "proxy": "http://localhost:3001"
+  "devDependencies": {
+    "redux-devtools-extension": "^2.13.9"
+  }
 }
diff --git a/client/public/favicon.ico b/client/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a
Binary files /dev/null and b/client/public/favicon.ico differ
diff --git a/client/public/index.html b/client/public/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..dd1ccfd4cd30a29aaa08b295d99be29cdeb29cf9
--- /dev/null
+++ b/client/public/index.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <!--
+      manifest.json provides metadata used when your web app is installed on a
+      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <!--
+      Notice the use of %PUBLIC_URL% in the tags above.
+      It will be replaced with the URL of the `public` folder during the build.
+      Only files inside the `public` folder can be referenced from the HTML.
+
+      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      work correctly both with client-side routing and a non-root public URL.
+      Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+    <title>React App</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>
diff --git a/client/public/manifest.json b/client/public/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..1f2f141fafdeb1d31d85b008ec5132840c5e6362
--- /dev/null
+++ b/client/public/manifest.json
@@ -0,0 +1,15 @@
+{
+  "short_name": "React App",
+  "name": "Create React App Sample",
+  "icons": [
+    {
+      "src": "favicon.ico",
+      "sizes": "64x64 32x32 24x24 16x16",
+      "type": "image/x-icon"
+    }
+  ],
+  "start_url": ".",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}
diff --git a/client/src/App.css b/client/src/App.css
deleted file mode 100644
index 6b027ba19972697f3baafec248da6e0033cd082c..0000000000000000000000000000000000000000
--- a/client/src/App.css
+++ /dev/null
@@ -1,18 +0,0 @@
-body { background: #f7f7f7; }
-.app { margin: 3em; width: 24em; }
-
-input,
-button {
-  padding: 0.5em 0.75em;
-  margin-right: 1em;
-  border-radius: 4px;
-  outline: none;
-  border: 1px solid transparent;
-  font: inherit;
-}
-
-input       { border-color: #888; }
-input:focus { border-color: #17f; }
-
-button        { background: #17f; color: #fff; }
-button:active { background: #15d; }
\ No newline at end of file
diff --git a/client/src/App.js b/client/src/App.js
deleted file mode 100644
index 8668b21a1c4067a331005fa02a991f378582a4db..0000000000000000000000000000000000000000
--- a/client/src/App.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import React, { useState } from "react";
-
-function App() {
-  const [word, setWord] = React.useState('software');
-  const [associations, setAssociations] = React.useState(null);
-
-  const getAssociations = () => {
-    fetch('/api/' + word)
-      .then(result => result.json())
-      .then(body => setAssociations(body));
-  };
-
-  return (
-    <div className="app">
-      <h1>Word Associations Map</h1>
-      <input value={word} onChange={e => setWord(e.target.value)} />
-      <button onClick={getAssociations}>Find Associations</button>
-      {associations && (
-        Object.keys(associations).length === 0
-          ? <p>No results</p>
-          : <div>
-            {Object.entries(associations).map(([association, score]) => (
-              <span style={{ fontSize: Math.pow(score, 2) / 200 }}>
-                {association + ' => ' + score}
-                {' '}
-              </span>
-            ))}
-          </div>
-      )}
-    </div>
-  );
-}
-
-export default App;
\ No newline at end of file
diff --git a/client/src/App.test.js b/client/src/App.test.js
deleted file mode 100644
index 1f03afeece5ac28064fa3c73a29215037465f789..0000000000000000000000000000000000000000
--- a/client/src/App.test.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
-  render(<App />);
-  const linkElement = screen.getByText(/learn react/i);
-  expect(linkElement).toBeInTheDocument();
-});
diff --git a/client/src/actions/auth.js b/client/src/actions/auth.js
new file mode 100644
index 0000000000000000000000000000000000000000..33ed2c0660009c180f1eace1e5706d2a905734cc
--- /dev/null
+++ b/client/src/actions/auth.js
@@ -0,0 +1,87 @@
+import {
+    REGISTER_SUCCESS,
+    REGISTER_FAIL,
+    LOGIN_SUCCESS,
+    LOGIN_FAIL,
+    LOGOUT,
+    SET_MESSAGE,
+  } from "./types";
+  
+  import AuthService from "../services/auth.service";
+  
+  export const register = (username, email, password) => (dispatch) => {
+    return AuthService.register(username, email, password).then(
+      (response) => {
+        dispatch({
+          type: REGISTER_SUCCESS,
+        });
+  
+        dispatch({
+          type: SET_MESSAGE,
+          payload: response.data.message,
+        });
+  
+        return Promise.resolve();
+      },
+      (error) => {
+        const message =
+          (error.response &&
+            error.response.data &&
+            error.response.data.message) ||
+          error.message ||
+          error.toString();
+  
+        dispatch({
+          type: REGISTER_FAIL,
+        });
+  
+        dispatch({
+          type: SET_MESSAGE,
+          payload: message,
+        });
+  
+        return Promise.reject();
+      }
+    );
+  };
+  
+  export const login = (username, password) => (dispatch) => {
+    return AuthService.login(username, password).then(
+      (data) => {
+        dispatch({
+          type: LOGIN_SUCCESS,
+          payload: { user: data },
+        });
+  
+        return Promise.resolve();
+      },
+      (error) => {
+        const message =
+          (error.response &&
+            error.response.data &&
+            error.response.data.message) ||
+          error.message ||
+          error.toString();
+  
+        dispatch({
+          type: LOGIN_FAIL,
+        });
+  
+        dispatch({
+          type: SET_MESSAGE,
+          payload: message,
+        });
+  
+        return Promise.reject();
+      }
+    );
+  };
+  
+  export const logout = () => (dispatch) => {
+    AuthService.logout();
+  
+    dispatch({
+      type: LOGOUT,
+    });
+  };
+  
\ No newline at end of file
diff --git a/client/src/actions/message.js b/client/src/actions/message.js
new file mode 100644
index 0000000000000000000000000000000000000000..9aba65774019ee025f6223c4698c116661f8162f
--- /dev/null
+++ b/client/src/actions/message.js
@@ -0,0 +1,10 @@
+import { SET_MESSAGE, CLEAR_MESSAGE } from "./types";
+
+export const setMessage = (message) => ({
+  type: SET_MESSAGE,
+  payload: message,
+});
+
+export const clearMessage = () => ({
+  type: CLEAR_MESSAGE,
+});
diff --git a/client/src/actions/types.js b/client/src/actions/types.js
new file mode 100644
index 0000000000000000000000000000000000000000..b34faca5da7fd2ecbafc70b40a5a88db7562b40e
--- /dev/null
+++ b/client/src/actions/types.js
@@ -0,0 +1,8 @@
+export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
+export const REGISTER_FAIL = "REGISTER_FAIL";
+export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
+export const LOGIN_FAIL = "LOGIN_FAIL";
+export const LOGOUT = "LOGOUT";
+
+export const SET_MESSAGE = "SET_MESSAGE";
+export const CLEAR_MESSAGE = "CLEAR_MESSAGE";
diff --git a/client/src/app.css b/client/src/app.css
new file mode 100644
index 0000000000000000000000000000000000000000..a7b0abfe88aa5553d10be565646a913b65300de7
--- /dev/null
+++ b/client/src/app.css
@@ -0,0 +1,33 @@
+label {
+    display: block;
+    margin-top: 10px;
+  }
+  
+  .card-container.card {
+    max-width: 350px !important;
+    padding: 40px 40px;
+  }
+  
+  .card {
+    background-color: #f7f7f7;
+    padding: 20px 25px 30px;
+    margin: 0 auto 25px;
+    margin-top: 50px;
+    -moz-border-radius: 2px;
+    -webkit-border-radius: 2px;
+    border-radius: 2px;
+    -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+    -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+    box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+  }
+  
+  .profile-img-card {
+    width: 96px;
+    height: 96px;
+    margin: 0 auto 10px;
+    display: block;
+    -moz-border-radius: 50%;
+    -webkit-border-radius: 50%;
+    border-radius: 50%;
+  }
+  
\ No newline at end of file
diff --git a/client/src/app.js b/client/src/app.js
new file mode 100644
index 0000000000000000000000000000000000000000..d0fc494125f90a819c5d8e21c784e12c1467a051
--- /dev/null
+++ b/client/src/app.js
@@ -0,0 +1,149 @@
+import React, { Component } from "react";
+import { connect } from "react-redux";
+import { Router, Switch, Route, Link } from "react-router-dom";
+
+import "bootstrap/dist/css/bootstrap.min.css";
+import "./app.css";
+
+import Login from "./components/login.component";
+import Register from "./components/register.component";
+import Home from "./components/home.component";
+import Profile from "./components/profile.component";
+import BoardUser from "./components/board-user.component";
+import BoardModerator from "./components/board-moderator.component";
+import BoardAdmin from "./components/board-admin.component";
+
+import { logout } from "./actions/auth";
+import { clearMessage } from "./actions/message";
+
+import { history } from './helpers/history';
+
+class App extends Component {
+  constructor(props) {
+    super(props);
+    this.logOut = this.logOut.bind(this);
+
+    this.state = {
+      showModeratorBoard: false,
+      showAdminBoard: false,
+      currentUser: undefined,
+    };
+
+    history.listen((location) => {
+      props.dispatch(clearMessage()); // clear message when changing location
+    });
+  }
+
+  componentDidMount() {
+    const user = this.props.user;
+
+    if (user) {
+      this.setState({
+        currentUser: user,
+        showModeratorBoard: user.roles.includes("ROLE_MODERATOR"),
+        showAdminBoard: user.roles.includes("ROLE_ADMIN"),
+      });
+    }
+  }
+
+  logOut() {
+    this.props.dispatch(logout());
+  }
+
+  render() {
+    const { currentUser, showModeratorBoard, showAdminBoard } = this.state;
+
+    return (
+      <Router history={history}>
+        <div>
+          <nav className="navbar navbar-expand navbar-dark bg-dark">
+            <Link to={"/"} className="navbar-brand">
+              STUDENT
+            </Link>
+            <div className="navbar-nav mr-auto">
+              <li className="nav-item">
+                <Link to={"/home"} className="nav-link">
+                  Home
+                </Link>
+              </li>
+
+              {showModeratorBoard && (
+                <li className="nav-item">
+                  <Link to={"/mod"} className="nav-link">
+                    Moderator Board
+                  </Link>
+                </li>
+              )}
+
+              {showAdminBoard && (
+                <li className="nav-item">
+                  <Link to={"/admin"} className="nav-link">
+                    Admin Board
+                  </Link>
+                </li>
+              )}
+
+              {currentUser && (
+                <li className="nav-item">
+                  <Link to={"/user"} className="nav-link">
+                    User
+                  </Link>
+                </li>
+              )}
+            </div>
+
+            {currentUser ? (
+              <div className="navbar-nav ml-auto">
+                <li className="nav-item">
+                  <Link to={"/profile"} className="nav-link">
+                    {currentUser.username}
+                  </Link>
+                </li>
+                <li className="nav-item">
+                  <a href="/login" className="nav-link" onClick={this.logOut}>
+                    LogOut
+                  </a>
+                </li>
+              </div>
+            ) : (
+              <div className="navbar-nav ml-auto">
+                <li className="nav-item">
+                  <Link to={"/login"} className="nav-link">
+                    Login
+                  </Link>
+                </li>
+
+                <li className="nav-item">
+                  <Link to={"/register"} className="nav-link">
+                    Sign Up
+                  </Link>
+                </li>
+              </div>
+            )}
+          </nav>
+
+          <div className="container mt-3">
+            <Switch>
+              <Route exact path={["/", "/home"]} component={Home} />
+              <Route exact path="/login" component={Login} />
+              <Route exact path="/register" component={Register} />
+              <Route exact path="/profile" component={Profile} />
+              <Route path="/user" component={BoardUser} />
+              <Route path="/mod" component={BoardModerator} />
+              <Route path="/admin" component={BoardAdmin} />
+            </Switch>
+          </div>
+        </div>
+      </Router>
+    );
+  }
+}
+
+function mapStateToProps(state) {
+  const { user } = state.auth;
+  return {
+    user,
+  };
+}
+
+export default connect(mapStateToProps)(App);
diff --git a/client/src/components/board-admin.component.js b/client/src/components/board-admin.component.js
new file mode 100644
index 0000000000000000000000000000000000000000..6093a64899b4421b3b019132b74bb37b3e36b488
--- /dev/null
+++ b/client/src/components/board-admin.component.js
@@ -0,0 +1,43 @@
+import React, { Component } from "react";
+
+import UserService from "../services/user.service";
+
+export default class BoardUser extends Component {
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      content: ""
+    };
+  }
+
+  componentDidMount() {
+    UserService.getUserBoard().then(
+      response => {
+        this.setState({
+          content: response.data
+        });
+      },
+      error => {
+        this.setState({
+          content:
+            (error.response &&
+              error.response.data &&
+              error.response.data.message) ||
+            error.message ||
+            error.toString()
+        });
+      }
+    );
+  }
+
+  render() {
+    return (
+      <div className="container">
+        <header className="jumbotron">
+          <h3>{this.state.content}</h3>
+        </header>
+      </div>
+    );
+  }
+}
diff --git a/client/src/components/board-moderator.component.js b/client/src/components/board-moderator.component.js
new file mode 100644
index 0000000000000000000000000000000000000000..4ac60f3c097b97cb62fc76f859da04ec6ef64702
--- /dev/null
+++ b/client/src/components/board-moderator.component.js
@@ -0,0 +1,43 @@
+import React, { Component } from "react";
+
+import UserService from "../services/user.service";
+
+export default class BoardUser extends Component {
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      content: ""
+    };
+  }
+
+  componentDidMount() {
+    UserService.getModeratorBoard().then(
+      response => {
+        this.setState({
+          content: response.data
+        });
+      },
+      error => {
+        this.setState({
+          content:
+            (error.response &&
+              error.response.data &&
+              error.response.data.message) ||
+            error.message ||
+            error.toString()
+        });
+      }
+    );
+  }
+
+  render() {
+    return (
+      <div className="container">
+        <header className="jumbotron">
+          <h3>{this.state.content}</h3>
+        </header>
+      </div>
+    );
+  }
+}
diff --git a/client/src/components/board-user.component.js b/client/src/components/board-user.component.js
new file mode 100644
index 0000000000000000000000000000000000000000..6093a64899b4421b3b019132b74bb37b3e36b488
--- /dev/null
+++ b/client/src/components/board-user.component.js
@@ -0,0 +1,43 @@
+import React, { Component } from "react";
+
+import UserService from "../services/user.service";
+
+export default class BoardUser extends Component {
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      content: ""
+    };
+  }
+
+  componentDidMount() {
+    UserService.getUserBoard().then(
+      response => {
+        this.setState({
+          content: response.data
+        });
+      },
+      error => {
+        this.setState({
+          content:
+            (error.response &&
+              error.response.data &&
+              error.response.data.message) ||
+            error.message ||
+            error.toString()
+        });
+      }
+    );
+  }
+
+  render() {
+    return (
+      <div className="container">
+        <header className="jumbotron">
+          <h3>{this.state.content}</h3>
+        </header>
+      </div>
+    );
+  }
+}
diff --git a/client/src/components/home.component.js b/client/src/components/home.component.js
new file mode 100644
index 0000000000000000000000000000000000000000..13d435ed70f023b210149c6290175134bde56743
--- /dev/null
+++ b/client/src/components/home.component.js
@@ -0,0 +1,84 @@
+import React, { Component } from "react";
+
+import UserService from "../services/user.service";
+
+export default class Home extends Component {
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      content: ""
+    };
+  }
+
+  componentDidMount() {
+    UserService.getPublicContent().then(
+      response => {
+        this.setState({
+          content: response.data
+        });
+      },
+      error => {
+        this.setState({
+          content:
+            (error.response && error.response.data) ||
+            error.message ||
+            error.toString()
+        });
+      }
+    );
+  }
+
+  getFormattedDate(date) {
+    var timestamp = Date.parse(date);
+    var formatDate = new Date(timestamp);
+    var dd = String(formatDate.getDate()).padStart(2, '0');
+    var mm = String(formatDate.getMonth() + 1).padStart(2, '0');
+    var yyyy = formatDate.getFullYear();
+    
+    return dd + '/' + mm + '/' + yyyy;
+}
+
+
+  render() {
+    return (
+      <div className="container">
+        <header className="jumbotron">
+            <h3>Welcome</h3>
+        </header>
+        <div>
+            { this.state.content ?
+                <table className="table">
+                    <thead>
+                        <tr>
+                          <th scope="col">#</th>
+                          <th scope="col">Nom utilisateur</th>
+                          <th scope="col">Nom</th>
+                          <th scope="col">Prenom</th>
+                          <th scope="col">Email</th>
+                          <th scope="col">Date de naissance</th>
+                          <th scope="col">Numero étudiant</th>
+                          <th scope="col">Date d'adhésion</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                {Object.entries(this.state.content).map(([key, student]) => (
+                    <tr key={key}>
+                        <th scope="row">{student.id}</th>
+                        <td>{student.nom_utilisateur}</td>
+                        <td>{student.nom}</td>
+                        <td>{student.prenom}</td>
+                        <td>{student.email}</td>
+                        <td>{this.getFormattedDate(student.date_naissance)}</td>
+                        <td>{student.numero_etudiant}</td>
+                        <td>{this.getFormattedDate(student.date_adhesion)}</td>
+                    </tr>
+                ))}
+                    </tbody>
+                </table> 
+            : "" }
+        </div>
+      </div>
+    );
+  }
+}
diff --git a/client/src/components/index.js b/client/src/components/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..bae61df19ea99cd8794ddedb75ef3e2761030b0d
--- /dev/null
+++ b/client/src/components/index.js
@@ -0,0 +1 @@
+export { SampleComponent } from './sample-component/sample-component';
\ No newline at end of file
diff --git a/client/src/components/login.component.js b/client/src/components/login.component.js
new file mode 100644
index 0000000000000000000000000000000000000000..ea3603ad675723af9d8cdd5ad7c4c09b4f40864c
--- /dev/null
+++ b/client/src/components/login.component.js
@@ -0,0 +1,163 @@
+import React, { Component } from "react";
+import { Redirect } from 'react-router-dom';
+
+import Form from "react-validation/build/form";
+import Input from "react-validation/build/input";
+import CheckButton from "react-validation/build/button";
+
+import { connect } from "react-redux";
+import { login } from "../actions/auth";
+
+const required = (value) => {
+  if (!value) {
+    return (
+      <div className="alert alert-danger" role="alert">
+        This field is required!
+      </div>
+    );
+  }
+};
+
+class Login extends Component {
+  constructor(props) {
+    super(props);
+    this.handleLogin = this.handleLogin.bind(this);
+    this.onChangeUsername = this.onChangeUsername.bind(this);
+    this.onChangePassword = this.onChangePassword.bind(this);
+
+    this.state = {
+      username: "",
+      password: "",
+      loading: false,
+    };
+  }
+
+  onChangeUsername(e) {
+    this.setState({
+      username: e.target.value,
+    });
+  }
+
+  onChangePassword(e) {
+    this.setState({
+      password: e.target.value,
+    });
+  }
+
+  handleLogin(e) {
+    e.preventDefault();
+
+    this.setState({
+      loading: true,
+    });
+
+    this.form.validateAll();
+
+    const { dispatch, history } = this.props;
+
+    if (this.checkBtn.context._errors.length === 0) {
+      dispatch(login(this.state.username, this.state.password))
+        .then(() => {
+          history.push("/profile");
+          window.location.reload();
+        })
+        .catch(() => {
+          this.setState({
+            loading: false
+          });
+        });
+    } else {
+      this.setState({
+        loading: false,
+      });
+    }
+  }
+
+  render() {
+    const { isLoggedIn, message } = this.props;
+
+    if (isLoggedIn) {
+      return <Redirect to="/profile" />;
+    }
+
+    return (
+      <div className="col-md-12">
+        <div className="card card-container">
+          <img
+            src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
+            alt="profile-img"
+            className="profile-img-card"
+          />
+
+          <Form
+            onSubmit={this.handleLogin}
+            ref={(c) => {
+              this.form = c;
+            }}
+          >
+            <div className="form-group">
+              <label htmlFor="username">Username</label>
+              <Input
+                type="text"
+                className="form-control"
+                name="username"
+                value={this.state.username}
+                onChange={this.onChangeUsername}
+                validations={[required]}
+              />
+            </div>
+
+            <div className="form-group">
+              <label htmlFor="password">Password</label>
+              <Input
+                type="password"
+                className="form-control"
+                name="password"
+                value={this.state.password}
+                onChange={this.onChangePassword}
+                validations={[required]}
+              />
+            </div>
+
+            <div className="form-group">
+              <button
+                className="btn btn-primary btn-block"
+                disabled={this.state.loading}
+              >
+                {this.state.loading && (
+                  <span className="spinner-border spinner-border-sm"></span>
+                )}
+                <span>Login</span>
+              </button>
+            </div>
+
+            {message && (
+              <div className="form-group">
+                <div className="alert alert-danger" role="alert">
+                  {message}
+                </div>
+              </div>
+            )}
+            <CheckButton
+              style={{ display: "none" }}
+              ref={(c) => {
+                this.checkBtn = c;
+              }}
+            />
+          </Form>
+        </div>
+      </div>
+    );
+  }
+}
+
+function mapStateToProps(state) {
+  const { isLoggedIn } = state.auth;
+  const { message } = state.message;
+  return {
+    isLoggedIn,
+    message
+  };
+}
+
+export default connect(mapStateToProps)(Login);
diff --git a/client/src/components/profile.component.js b/client/src/components/profile.component.js
new file mode 100644
index 0000000000000000000000000000000000000000..2265696a19af38fb4d4ddc6d4577b6bbe224cb53
--- /dev/null
+++ b/client/src/components/profile.component.js
@@ -0,0 +1,48 @@
+import React, { Component } from "react";
+import { Redirect } from 'react-router-dom';
+import { connect } from "react-redux";
+
+class Profile extends Component {
+
+  render() {
+    const { user: currentUser } = this.props;
+
+    if (!currentUser) {
+      return <Redirect to="/login" />;
+    }
+
+    return (
+      <div className="container">
+        <header className="jumbotron">
+          <h3>
+            <strong>{currentUser.username}</strong> Profile
+          </h3>
+        </header>
+        <p>
+          <strong>Token:</strong> {currentUser.accessToken.substring(0, 20)} ...{" "}
+          {currentUser.accessToken.substr(currentUser.accessToken.length - 20)}
+        </p>
+        <p>
+          <strong>Id:</strong> {currentUser.id}
+        </p>
+        <p>
+          <strong>Email:</strong> {currentUser.email}
+        </p>
+        <strong>Authorities:</strong>
+        <ul>
+          {currentUser.roles &&
+            currentUser.roles.map((role, index) => <li key={index}>{role}</li>)}
+        </ul>
+      </div>
+    );
+  }
+}
+
+function mapStateToProps(state) {
+  const { user } = state.auth;
+  return {
+    user,
+  };
+}
+
+export default connect(mapStateToProps)(Profile);
diff --git a/client/src/components/register.component.js b/client/src/components/register.component.js
new file mode 100644
index 0000000000000000000000000000000000000000..042e63b1ca1c102e90807c87816881e993e3584d
--- /dev/null
+++ b/client/src/components/register.component.js
@@ -0,0 +1,200 @@
+import React, { Component } from "react";
+import Form from "react-validation/build/form";
+import Input from "react-validation/build/input";
+import CheckButton from "react-validation/build/button";
+import { isEmail } from "validator";
+
+import { connect } from "react-redux";
+import { register } from "../actions/auth";
+
+const required = (value) => {
+  if (!value) {
+    return (
+      <div className="alert alert-danger" role="alert">
+        This field is required!
+      </div>
+    );
+  }
+};
+
+const email = (value) => {
+  if (!isEmail(value)) {
+    return (
+      <div className="alert alert-danger" role="alert">
+        This is not a valid email.
+      </div>
+    );
+  }
+};
+
+const vusername = (value) => {
+  if (value.length < 3 || value.length > 20) {
+    return (
+      <div className="alert alert-danger" role="alert">
+        The username must be between 3 and 20 characters.
+      </div>
+    );
+  }
+};
+
+const vpassword = (value) => {
+  if (value.length < 6 || value.length > 40) {
+    return (
+      <div className="alert alert-danger" role="alert">
+        The password must be between 6 and 40 characters.
+      </div>
+    );
+  }
+};
+
+class Register extends Component {
+  constructor(props) {
+    super(props);
+    this.handleRegister = this.handleRegister.bind(this);
+    this.onChangeUsername = this.onChangeUsername.bind(this);
+    this.onChangeEmail = this.onChangeEmail.bind(this);
+    this.onChangePassword = this.onChangePassword.bind(this);
+
+    this.state = {
+      username: "",
+      email: "",
+      password: "",
+      successful: false,
+    };
+  }
+
+  onChangeUsername(e) {
+    this.setState({
+      username: e.target.value,
+    });
+  }
+
+  onChangeEmail(e) {
+    this.setState({
+      email: e.target.value,
+    });
+  }
+
+  onChangePassword(e) {
+    this.setState({
+      password: e.target.value,
+    });
+  }
+
+  handleRegister(e) {
+    e.preventDefault();
+
+    this.setState({
+      successful: false,
+    });
+
+    this.form.validateAll();
+
+    if (this.checkBtn.context._errors.length === 0) {
+      this.props
+        .dispatch(
+          register(this.state.username, this.state.email, this.state.password)
+        )
+        .then(() => {
+          this.setState({
+            successful: true,
+          });
+        })
+        .catch(() => {
+          this.setState({
+            successful: false,
+          });
+        });
+    }
+  }
+
+  render() {
+    const { message } = this.props;
+
+    return (
+      <div className="col-md-12">
+        <div className="card card-container">
+          <img
+            src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
+            alt="profile-img"
+            className="profile-img-card"
+          />
+
+          <Form
+            onSubmit={this.handleRegister}
+            ref={(c) => {
+              this.form = c;
+            }}
+          >
+            {!this.state.successful && (
+              <div>
+                <div className="form-group">
+                  <label htmlFor="username">Username</label>
+                  <Input
+                    type="text"
+                    className="form-control"
+                    name="username"
+                    value={this.state.username}
+                    onChange={this.onChangeUsername}
+                    validations={[required, vusername]}
+                  />
+                </div>
+
+                <div className="form-group">
+                  <label htmlFor="email">Email</label>
+                  <Input
+                    type="text"
+                    className="form-control"
+                    name="email"
+                    value={this.state.email}
+                    onChange={this.onChangeEmail}
+                    validations={[required, email]}
+                  />
+                </div>
+
+                <div className="form-group">
+                  <label htmlFor="password">Password</label>
+                  <Input
+                    type="password"
+                    className="form-control"
+                    name="password"
+                    value={this.state.password}
+                    onChange={this.onChangePassword}
+                    validations={[required, vpassword]}
+                  />
+                </div>
+
+                <div className="form-group">
+                  <button className="btn btn-primary btn-block">Sign Up</button>
+                </div>
+              </div>
+            )}
+
+            {message && (
+              <div className="form-group">
+                <div className={ this.state.successful ? "alert alert-success" : "alert alert-danger" } role="alert">
+                  {message}
+                </div>
+              </div>
+            )}
+            <CheckButton
+              style={{ display: "none" }}
+              ref={(c) => {
+                this.checkBtn = c;
+              }}
+            />
+          </Form>
+        </div>
+      </div>
+    );
+  }
+}
+
+function mapStateToProps(state) {
+  const { message } = state.message;
+  return {
+    message,
+  };
+}
+
+export default connect(mapStateToProps)(Register);
diff --git a/client/src/components/sample-component/sample-component.jsx b/client/src/components/sample-component/sample-component.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d8a3035eea72a02ca8efc4246de68d4a061d3c03
--- /dev/null
+++ b/client/src/components/sample-component/sample-component.jsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import axios from 'axios';
+
+function callServer() {
+  axios.get(`http://localhost:${process.env.REACT_APP_SERVER_PORT}/test`, {
+    params: {
+      table: 'sample',
+    },
+  }).then((response) => {
+    console.log(response.data);
+  });
+}
+
+export function SampleComponent() {
+  return (
+    <div>
+      This is a sample component
+      <table id='students'>
+          <tbody>
+            {callServer()}
+          </tbody>
+      </table>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/client/src/helpers/history.js b/client/src/helpers/history.js
new file mode 100644
index 0000000000000000000000000000000000000000..4c002e03aa6d7033cf1dfca3175cfacc0e38755b
--- /dev/null
+++ b/client/src/helpers/history.js
@@ -0,0 +1,3 @@
+import { createBrowserHistory } from "history";
+
+export const history = createBrowserHistory();
diff --git a/client/src/index.css b/client/src/index.css
deleted file mode 100644
index 6b027ba19972697f3baafec248da6e0033cd082c..0000000000000000000000000000000000000000
--- a/client/src/index.css
+++ /dev/null
@@ -1,18 +0,0 @@
-body { background: #f7f7f7; }
-.app { margin: 3em; width: 24em; }
-
-input,
-button {
-  padding: 0.5em 0.75em;
-  margin-right: 1em;
-  border-radius: 4px;
-  outline: none;
-  border: 1px solid transparent;
-  font: inherit;
-}
-
-input       { border-color: #888; }
-input:focus { border-color: #17f; }
-
-button        { background: #17f; color: #fff; }
-button:active { background: #15d; }
\ No newline at end of file
diff --git a/client/src/index.js b/client/src/index.js
index ef2edf8ea3fc42258464231e29140c8723458c1e..b63b6509ff7de50978efa95de4442c678f97791c 100644
--- a/client/src/index.js
+++ b/client/src/index.js
@@ -1,17 +1,21 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
-import './index.css';
-import App from './App';
-import reportWebVitals from './reportWebVitals';
+import App from './app';
+import * as serviceWorker from './serviceWorker';
+import { Provider } from 'react-redux';
+import store from './store';
+
 
 ReactDOM.render(
-  <React.StrictMode>
-    <App />
-  </React.StrictMode>,
+  
+    <Provider store={store}>
+      <App />
+    </Provider>
+,
   document.getElementById('root')
 );
 
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: https://bit.ly/CRA-PWA
+serviceWorker.unregister();
diff --git a/client/src/logo.svg b/client/src/logo.svg
deleted file mode 100644
index 9dfc1c058cebbef8b891c5062be6f31033d7d186..0000000000000000000000000000000000000000
--- a/client/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
diff --git a/client/src/reducers/auth.js b/client/src/reducers/auth.js
new file mode 100644
index 0000000000000000000000000000000000000000..39b7fcf047e7bb10eb2ad094eeb638916d3beefd
--- /dev/null
+++ b/client/src/reducers/auth.js
@@ -0,0 +1,51 @@
+import {
+    REGISTER_SUCCESS,
+    REGISTER_FAIL,
+    LOGIN_SUCCESS,
+    LOGIN_FAIL,
+    LOGOUT,
+  } from "../actions/types";
+  
+  const user = JSON.parse(localStorage.getItem("user"));
+  
+  const initialState = user
+    ? { isLoggedIn: true, user }
+    : { isLoggedIn: false, user: null };
+  
+  export default function (state = initialState, action) {
+    const { type, payload } = action;
+  
+    switch (type) {
+      case REGISTER_SUCCESS:
+        return {
+          ...state,
+          isLoggedIn: false,
+        };
+      case REGISTER_FAIL:
+        return {
+          ...state,
+          isLoggedIn: false,
+        };
+      case LOGIN_SUCCESS:
+        return {
+          ...state,
+          isLoggedIn: true,
+          user: payload.user,
+        };
+      case LOGIN_FAIL:
+        return {
+          ...state,
+          isLoggedIn: false,
+          user: null,
+        };
+      case LOGOUT:
+        return {
+          ...state,
+          isLoggedIn: false,
+          user: null,
+        };
+      default:
+        return state;
+    }
+  }
+  
\ No newline at end of file
diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..8cb74c9010357a25a646799a6e4c3b1d7a0891ae
--- /dev/null
+++ b/client/src/reducers/index.js
@@ -0,0 +1,8 @@
+import { combineReducers } from "redux";
+import auth from "./auth";
+import message from "./message";
+
+export default combineReducers({
+  auth,
+  message,
+});
diff --git a/client/src/reducers/message.js b/client/src/reducers/message.js
new file mode 100644
index 0000000000000000000000000000000000000000..3740826b7f264af3ad938c47a69ee7b46e829af1
--- /dev/null
+++ b/client/src/reducers/message.js
@@ -0,0 +1,18 @@
+import { SET_MESSAGE, CLEAR_MESSAGE } from "../actions/types";
+
+const initialState = {};
+
+export default function (state = initialState, action) {
+  const { type, payload } = action;
+
+  switch (type) {
+    case SET_MESSAGE:
+      return { message: payload };
+
+    case CLEAR_MESSAGE:
+      return { message: "" };
+
+    default:
+      return state;
+  }
+}
diff --git a/client/src/reportWebVitals.js b/client/src/reportWebVitals.js
deleted file mode 100644
index 5253d3ad9e6be6690549cb255f5952337b02401d..0000000000000000000000000000000000000000
--- a/client/src/reportWebVitals.js
+++ /dev/null
@@ -1,13 +0,0 @@
-const reportWebVitals = onPerfEntry => {
-  if (onPerfEntry && onPerfEntry instanceof Function) {
-    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
-      getCLS(onPerfEntry);
-      getFID(onPerfEntry);
-      getFCP(onPerfEntry);
-      getLCP(onPerfEntry);
-      getTTFB(onPerfEntry);
-    });
-  }
-};
-
-export default reportWebVitals;
diff --git a/client/src/serviceWorker.js b/client/src/serviceWorker.js
new file mode 100644
index 0000000000000000000000000000000000000000..f8c7e50c201765c456ddbf21e9ea5b3e6a936920
--- /dev/null
+++ b/client/src/serviceWorker.js
@@ -0,0 +1,135 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+  window.location.hostname === 'localhost' ||
+    // [::1] is the IPv6 localhost address.
+    window.location.hostname === '[::1]' ||
+    // 127.0.0.1/8 is considered localhost for IPv4.
+    window.location.hostname.match(
+      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+    )
+);
+
+export function register(config) {
+  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+    // The URL constructor is available in all browsers that support SW.
+    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+    if (publicUrl.origin !== window.location.origin) {
+      // Our service worker won't work if PUBLIC_URL is on a different origin
+      // from what our page is served on. This might happen if a CDN is used to
+      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+      return;
+    }
+
+    window.addEventListener('load', () => {
+      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+      if (isLocalhost) {
+        // This is running on localhost. Let's check if a service worker still exists or not.
+        checkValidServiceWorker(swUrl, config);
+
+        // Add some additional logging to localhost, pointing developers to the
+        // service worker/PWA documentation.
+        navigator.serviceWorker.ready.then(() => {
+          console.log(
+            'This web app is being served cache-first by a service ' +
+              'worker. To learn more, visit https://bit.ly/CRA-PWA'
+          );
+        });
+      } else {
+        // Is not localhost. Just register service worker
+        registerValidSW(swUrl, config);
+      }
+    });
+  }
+}
+
+function registerValidSW(swUrl, config) {
+  navigator.serviceWorker
+    .register(swUrl)
+    .then(registration => {
+      registration.onupdatefound = () => {
+        const installingWorker = registration.installing;
+        if (installingWorker == null) {
+          return;
+        }
+        installingWorker.onstatechange = () => {
+          if (installingWorker.state === 'installed') {
+            if (navigator.serviceWorker.controller) {
+              // At this point, the updated precached content has been fetched,
+              // but the previous service worker will still serve the older
+              // content until all client tabs are closed.
+              console.log(
+                'New content is available and will be used when all ' +
+                  'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+              );
+
+              // Execute callback
+              if (config && config.onUpdate) {
+                config.onUpdate(registration);
+              }
+            } else {
+              // At this point, everything has been precached.
+              // It's the perfect time to display a
+              // "Content is cached for offline use." message.
+              console.log('Content is cached for offline use.');
+
+              // Execute callback
+              if (config && config.onSuccess) {
+                config.onSuccess(registration);
+              }
+            }
+          }
+        };
+      };
+    })
+    .catch(error => {
+      console.error('Error during service worker registration:', error);
+    });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+  // Check if the service worker can be found. If it can't reload the page.
+  fetch(swUrl)
+    .then(response => {
+      // Ensure service worker exists, and that we really are getting a JS file.
+      const contentType = response.headers.get('content-type');
+      if (
+        response.status === 404 ||
+        (contentType != null && contentType.indexOf('javascript') === -1)
+      ) {
+        // No service worker found. Probably a different app. Reload the page.
+        navigator.serviceWorker.ready.then(registration => {
+          registration.unregister().then(() => {
+            window.location.reload();
+          });
+        });
+      } else {
+        // Service worker found. Proceed as normal.
+        registerValidSW(swUrl, config);
+      }
+    })
+    .catch(() => {
+      console.log(
+        'No internet connection found. App is running in offline mode.'
+      );
+    });
+}
+
+export function unregister() {
+  if ('serviceWorker' in navigator) {
+    navigator.serviceWorker.ready.then(registration => {
+      registration.unregister();
+    });
+  }
+}
diff --git a/client/src/services/auth-header.js b/client/src/services/auth-header.js
new file mode 100644
index 0000000000000000000000000000000000000000..31f6d1a009ec65a4ff9573855e609f7f48c01e03
--- /dev/null
+++ b/client/src/services/auth-header.js
@@ -0,0 +1,10 @@
+export default function authHeader() {
+    const user = JSON.parse(localStorage.getItem('user'));
+
+    if (user && user.accessToken) {
+        // for Node.js Express back-end
+        return { 'x-access-token': user.accessToken };
+    } else {
+        return {};
+    }
+}
\ No newline at end of file
diff --git a/client/src/services/auth.service.js b/client/src/services/auth.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..98388a518f38d30f56259dc9b3862b286dc6009b
--- /dev/null
+++ b/client/src/services/auth.service.js
@@ -0,0 +1,31 @@
+import axios from "axios";
+
+const API_URL = "http://localhost:8000/api/auth/";
+
+class AuthService {
+  login(username, password) {
+    return axios
+      .post(API_URL + "signin", { username, password })
+      .then((response) => {
+        if (response.data.accessToken) {
+          localStorage.setItem("user", JSON.stringify(response.data));
+        }
+
+        return response.data;
+      });
+  }
+
+  logout() {
+    localStorage.removeItem("user");
+  }
+
+  register(username, email, password) {
+    return axios.post(API_URL + "signup", {
+      username,
+      email,
+      password,
+    });
+  }
+}
+
+export default new AuthService();
diff --git a/client/src/services/user.service.js b/client/src/services/user.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..2eaafcb6a52805e5485b4dd7b6266beade195a79
--- /dev/null
+++ b/client/src/services/user.service.js
@@ -0,0 +1,24 @@
+import axios from 'axios';
+import authHeader from './auth-header';
+
+const API_URL = 'http://localhost:8000/api/auth/';
+
+class UserService {
+getPublicContent() {
+    return axios.get(API_URL + 'all');
+}
+
+getUserBoard() {
+    return axios.get(API_URL + 'user', { headers: authHeader() });
+}
+
+getModeratorBoard() {
+    return axios.get(API_URL + 'mod', { headers: authHeader() });
+}
+
+getAdminBoard() {
+    return axios.get(API_URL + 'admin', { headers: authHeader() });
+}
+}
+
+export default new UserService();
diff --git a/client/src/setupTests.js b/client/src/setupTests.js
deleted file mode 100644
index 8f2609b7b3e0e3897ab3bcaad13caf6876e48699..0000000000000000000000000000000000000000
--- a/client/src/setupTests.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';
diff --git a/client/src/store.js b/client/src/store.js
new file mode 100644
index 0000000000000000000000000000000000000000..53c4739b1f9be83df8b324c9f96617d91ed73185
--- /dev/null
+++ b/client/src/store.js
@@ -0,0 +1,13 @@
+import { createStore, applyMiddleware } from "redux";
+import { composeWithDevTools } from "redux-devtools-extension";
+import thunk from "redux-thunk";
+import rootReducer from "./reducers";
+
+const middleware = [thunk];
+
+const store = createStore(
+  rootReducer,
+  composeWithDevTools(applyMiddleware(...middleware))
+);
+
+export default store;
diff --git a/client/webpack.config.js b/client/webpack.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..5b8652859571b0268ee85bdb0e455d1b6b6b6fa4
--- /dev/null
+++ b/client/webpack.config.js
@@ -0,0 +1,10 @@
+const path = require('path');
+
+module.exports = {
+  resolve: {
+    alias: {
+      'components': path.resolve(__dirname, 'src/components'),
+    },
+    extensions: ['.jsx', '.js', '.scss', '.json'],
+  },
+};
\ No newline at end of file
diff --git a/db/student.sql b/db/student.sql
new file mode 100644
index 0000000000000000000000000000000000000000..06f0743ecb4a990bf69a1403afc987516b4f540c
--- /dev/null
+++ b/db/student.sql
@@ -0,0 +1,42 @@
+--
+-- Structure de la table `STUDENT`
+--
+
+CREATE TABLE `STUDENT` (
+  `id` int(11) NOT NULL,
+  `nom_utilisateur` varchar(255) NOT NULL,
+  `mdp` varchar(255) NOT NULL,
+  `nom` varchar(255) NOT NULL,
+  `prenom` varchar(255) NOT NULL,
+  `email` varchar(255) NOT NULL,
+  `date_naissance` date NOT NULL,
+  `numero_etudiant` int(20) NOT NULL,
+  `photo` varchar(255) NOT NULL,
+  `date_adhesion` date NOT NULL,
+  `created_at` datetime  NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `modified_at` datetime  NOT NULL DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Déchargement des données de la table `STUDENT`
+--
+
+INSERT INTO `STUDENT` (`id`, `nom_utilisateur`, `mdp`, `nom`, `prenom`, `email`, `date_naissance`, `numero_etudiant`, `photo`, `date_adhesion`) VALUES
+(1, 'jmercier', '36361127680f6e70448d262faf47c0459854498a74c1e2457dc7bc3f566972ce', 'Mercier', 'Jean', 'jean.mercier@etu.unistra.fr', '1999-03-02', 11223344, 'https://us.123rf.com/450wm/salamatik/salamatik1801/salamatik180100019/92979836-ic%C3%B4ne-de-visage-anonyme-de-profil-personne-silhouette-grise-avatar-par-d%C3%A9faut-masculin-photo-placeho.jpg?ver=6', '2021-09-10'),
+(2, 'cbertin', 'bbb1dcf581d4af52a77eb3d2e5fee4aa40aa65ca4afa8b2ece3e511473f82e78', 'Bertin', 'Caroline', 'c.bertin@etu.unistra.fr', '1998-09-07', 99887766, 'https://us.123rf.com/450wm/salamatik/salamatik1801/salamatik180100019/92979836-ic%C3%B4ne-de-visage-anonyme-de-profil-personne-silhouette-grise-avatar-par-d%C3%A9faut-masculin-photo-placeho.jpg?ver=6', '2021-09-06'),
+(3, 'mschneider', '7d68e6cb489f5347a31ecb7cce7e40de0c7f1eaba1142ea4cca35f3807ad9167', 'Schneider', 'Marine', 'm.schneider@etu.unistra.fr', '1999-02-06', 12345678, 'https://us.123rf.com/450wm/salamatik/salamatik1801/salamatik180100019/92979836-ic%C3%B4ne-de-visage-anonyme-de-profil-personne-silhouette-grise-avatar-par-d%C3%A9faut-masculin-photo-placeho.jpg?ver=6', '2021-10-01');
+
+--
+-- Index pour la table `STUDENT`
+--
+ALTER TABLE `STUDENT`
+  ADD PRIMARY KEY (`id`),
+  ADD UNIQUE KEY `nom_utilisateur` (`nom_utilisateur`),
+  ADD UNIQUE KEY `numero_etudiant` (`numero_etudiant`);
+
+--
+-- AUTO_INCREMENT pour la table `STUDENT`
+--
+ALTER TABLE `STUDENT`
+  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
+COMMIT;
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..152e4ccc1b1282ac1e460067af37e15aa4213cb9
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,55 @@
+version: "3.4"
+
+services:
+  mysql:
+    image: mysql:5.7
+    ports:
+      - 3306:3306
+    restart: unless-stopped
+    env_file:
+      - ./.env
+    volumes:
+      - ./db/student.sql:/docker-entrypoint-initdb.d/student.sql
+
+  phpmyadmin:
+    image: phpmyadmin/phpmyadmin
+    depends_on:
+      - mysql
+    env_file:
+      - ./.env
+    links: 
+      - mysql
+    ports:
+      - 8080:80
+    restart: always
+
+  server:
+    build: ./server
+    expose:
+      - 8000
+    ports:
+      - 8000:8000
+    volumes:
+      - ./server:/usr/src/app/
+    depends_on:
+      - mysql
+    env_file:
+      - ./.env
+    command: >
+      sh -c "npm install && npm start"
+
+  client:
+    build: ./client
+    expose:
+      - 3000
+    ports:
+      - 3000:3000
+    depends_on:
+      - server
+    env_file:
+      - ./.env
+    command: npm start
+
+
+volumes:
+  node_modules:
\ No newline at end of file
diff --git a/server/.dockerignore b/server/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..b512c09d476623ff4bf8d0d63c29b784925dbdf8
--- /dev/null
+++ b/server/.dockerignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/server/Dockerfile b/server/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..55b3addb055dc26e644707c16f892f76df6ba188
--- /dev/null
+++ b/server/Dockerfile
@@ -0,0 +1,13 @@
+FROM node:10-alpine
+
+RUN mkdir -p /usr/src/app
+WORKDIR /usr/src/app
+
+RUN npm install
+
+COPY package*.json /usr/src/app
+RUN npm install
+
+COPY . /usr/src/app
+
+CMD ["npm", "start"]
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
index 490d97c6cb8bb923dc6053b6b054b8d28b885594..de0825df2fe7731196d02ab1f8825fa9a398e4b5 100644
--- a/server/index.js
+++ b/server/index.js
@@ -1,25 +1,29 @@
+const cors = require('cors');
 const express = require('express');
+const mysql = require('mysql');
+
 const app = express();
-const port = 3001;
-const unirest = require("unirest");
 
-app.get('/api/:word', (req, res) => {
-  /*const request = unirest("GET", "https://twinword-word-associations-v1.p.rapidapi.com/associations/");
-  request.query({ "entry": req.params.word });
-  request.headers({
-    "x-rapidapi-host": "twinword-word-associations-v1.p.rapidapi.com",
-    "x-rapidapi-key": "YOUR_RAPID_API_KEY_GOES_HERE",
-    "useQueryString": true
-  });
+const pool = mysql.createPool({
+  host: process.env.MYSQL_HOST_IP,
+  user: process.env.MYSQL_USER,
+  password: process.env.MYSQL_PASSWORD,
+  database: process.env.MYSQL_DATABASE,
+});
 
-  request.end(function (response) {
-    if (response.error) throw new Error(response.error);
+app.use(cors());
 
-    res.json(response.body.associations_scored || {});
-  });*/
-  res.json({ "entry": req.params.word });
+app.listen(process.env.REACT_APP_SERVER_PORT, () => {
+  console.log(`App server now listening on port ${process.env.REACT_APP_SERVER_PORT}`);
 });
 
-app.listen(port, () => {
-  console.log(`Example app listening at http://localhost:${port}`);
-});
\ No newline at end of file
+
+app.get('/api/auth/all', (req, res) => {
+  pool.query(`select * from STUDENT`, (err, results) => {
+    if (err) {
+      return res.send(err);
+    } else {
+      return res.send(results);
+    }
+  });
+});
diff --git a/server/package.json b/server/package.json
index e0a5da7d1980300cea7291867b1f302031f26760..086b4c48bb24c2e75e19ed65427133710aec5628 100644
--- a/server/package.json
+++ b/server/package.json
@@ -4,13 +4,16 @@
   "description": "",
   "main": "index.js",
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "preinstall": "npm i nodemon -g",
+    "start": "nodemon index.js"
   },
   "keywords": [],
   "author": "",
   "license": "ISC",
   "dependencies": {
+    "cors": "^2.8.5",
     "express": "^4.17.1",
-    "unirest": "^0.6.0"
+    "mysql": "^2.17.1",
+    "nodemon": "^2.0.12"
   }
 }