diff --git a/README.md b/README.md
index 962bfd07799bf2bcd0338b4c1cf3cc991c66123a..e8b843ab591261b6a5f7c9d2f21943ae194ed0bb 100644
--- a/README.md
+++ b/README.md
@@ -96,16 +96,16 @@ Une authentification sans mot de passe permet l'identification de l'utilisateur
 
 ### Interface REST API
 
-Voici l'API REST CRUD exposée par le backend
-
-| Verbe HTTP | Endpoint                 | Données     | Description |
-|:-----------|:-------------------------|:------------|:------------|
-| GET        | emails/                  |             | Retourne la liste des mails des utilisateurs |
-| GET        | evaluated-jokes/         |             | Retourne les blagues les mieux notées |
-| GET        | evaluated-jokes/*:email* | email*      | Retourne les blagues notées par l'utilisateur |
-| POST       | evaluated-jokes/         | email*, id* | Ajout d'une nouvelle note à la blague (id) |
-| PUT        | evaluated-jokes/         | email*, id* | Modification de la note de la blague (id) |
-| DELETE     | evaluated-jokes/         | email*, id* | Suppression de la note de la blague (id) |
+Voici l'API REST CRUD exposée par le backend sur /api/v1/
+
+| Verbe HTTP | Endpoint                     | Données               | Description |
+|:-----------|:-----------------------------|:----------------------|:------------|
+| GET        | emails/                      |                       | Retourne la liste des mails des utilisateurs |
+| GET        | jokes/:limit                 |                       | Retourne les blagues les mieux notées |
+| GET        | jokes/*:userEmail*/:limit    |                       | Retourne les blagues notées par l'utilisateur |
+| POST       | jokes/                       | userEmail\*, jokeId\* | Ajout d'une nouvelle note à la blague (jokeId) |
+| DELETE     | jokes/                       | userEmail\*, jokeId\* | Suppression de la note de la blague (jokeId) |
+| GET        | likes/*:userEmail*/*:jokeId* |                       | Retourne la note d'une blague (jokeId) |
 
 \* Obligatoire
 
diff --git a/backend/modules/jokes.js b/backend/modules/jokes.js
new file mode 100644
index 0000000000000000000000000000000000000000..420b6568f369a925c3eadab6422ff1ae62f31e8f
--- /dev/null
+++ b/backend/modules/jokes.js
@@ -0,0 +1,21 @@
+let persist = require('./persist');
+
+exports.getBestJokes = function(limit = 20) {
+  return persist.getIdsOrderByOccurence().slice(0, limit);
+}
+
+exports.getJokesFor = function(email, limit = 20) {
+  return persist.getIdsForUser(email).slice(0, limit);
+}
+
+exports.getJokeFor = function(jokeId, email) {
+  return persist.isIdExistsForUser(jokeId, email);
+}
+
+exports.saveJokeFor = function(jokeId, email) {
+  return persist.addIdToUser(jokeId, email);
+}
+
+exports.deleteJokeFor = function(jokeId, email) {
+  return persist.removeIdToUser(jokeId, email);
+}
diff --git a/backend/modules/persist.js b/backend/modules/persist.js
new file mode 100644
index 0000000000000000000000000000000000000000..0dae2e1555fa5022da27913da43d0857520d7c54
--- /dev/null
+++ b/backend/modules/persist.js
@@ -0,0 +1,66 @@
+let database = {
+  'dev@hesge.ch': [
+    1,2,3
+  ],
+  'test@hesge.ch': [
+    2,4,6
+  ]
+}
+
+exports.isIdExistsForUser = function(id, email) {
+  createArrayWhenInexistant(email);
+
+  return isIdExists(id, email);
+}
+
+exports.addIdToUser = function(id, email) {
+  createArrayWhenInexistant(email);
+
+  if(!isIdExists(id, email)) {
+    database[email].push(id);
+    return true;
+  }
+  return false;
+}
+
+exports.removeIdToUser = function(id, email) {
+  createArrayWhenInexistant(email);
+
+  if(isIdExists(id, email)) {
+    const index = database[email].indexOf(parseInt(id))
+    database[email].splice(index, 1);
+    return true;
+  }
+  return false;
+}
+
+exports.getIdsForUser = function(email) {
+  createArrayWhenInexistant(email);
+
+  return database[email].sort();
+}
+
+exports.getIdsOrderByOccurence = function(email) {
+  createArrayWhenInexistant(email);
+
+  var ids = Object.values(database)
+    .flat()
+    .reduce((acc, curr) => {
+      acc[curr] ? acc[curr]++ : acc[curr] = 1
+      return acc
+    }, {});
+
+  return Object.keys(ids)
+    .sort()
+    .map(key => ids[key]);
+}
+
+function createArrayWhenInexistant(email) {
+  if(!Array.isArray(database[email])) {
+    database[email] = [];
+  }
+}
+
+function isIdExists(id, email) {
+  return database[email].indexOf(parseInt(id)) !== -1;
+}
diff --git a/backend/server.js b/backend/server.js
index b43b629e8b68d41aec08184ac460220171d29355..e6234d7353d88ee1feece14af56cd57cc9be5ca2 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -1,7 +1,123 @@
 const express = require('express');
+const bodyParser = require('body-parser');
+const jokes = require('./modules/jokes');
+
+const PORT_NUMBER = 8080;
 
 let app = express()
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
 app.use(express.static('../frontend'))
 
-app.listen(8080);
-console.log('Server started')
+/*
+* API Documentation
+*/
+app.get('/api', function(request, response) {
+    response.setHeader('Content-Type', 'text/plain');
+    response.send(`
+    Voici l'API REST CRUD exposée par le backend sur /api/v1/
+    
+    | Verbe HTTP | Endpoint                     | Données             | Description                                    |
+    |:-----------|:-----------------------------|:--------------------|:-----------------------------------------------|
+    | GET        | emails/                      |                     | Retourne la liste des mails des utilisateurs   |
+    | GET        | jokes/:limit                 |                     | Retourne les blagues les mieux notées          |
+    | GET        | jokes/*:userEmail*/:limit    |                     | Retourne les blagues notées par l'utilisateur  |
+    | POST       | jokes/                       | userEmail\*, jokeId\* | Ajout d'une nouvelle note à la blague (jokeId) |
+    | DELETE     | jokes/                       | userEmail\*, jokeId\* | Suppression de la note de la blague (jokeId)   |
+    | GET        | likes/*:userEmail*/*:jokeId* |                     | Retourne la note d'une blague (jokeId)         |
+      `);
+  });
+
+/*
+* Best jokes
+* params limit the number of jokes returned
+*/
+app.get('/api/v1/jokes/:limit?', function(request, response) {
+
+    console.log('Best jokes', request.params);
+
+    const limit = request.params.limit ? request.params.limit : 10;
+    response.json(jokes.getBestJokes(limit));
+  });
+  
+/*
+* Best jokes of specific users
+* params userEmail* user's e-mail
+* params limit the number of jokes returned
+*/
+app.get('/api/v1/jokes/:userEmail/:limit?', function(request, response) {
+
+    console.log('My jokes', request.params);
+
+    if(request.params.userEmail !== undefined) {
+
+      response.json(jokes.getJokesFor(request.params.userEmail, request.params.limit));
+    }
+
+    response.status(400).end(); // Bad request
+  });
+
+/*
+* Like a joke
+* params userEmail* user's e-mail
+* params jokeId* joke id to like
+*/
+app.post('/api/v1/jokes', function(request, response) {
+
+    console.log('Like joke', request.body);
+
+    if(request.body.userEmail !== undefined && request.body.jokeId !== undefined) {
+
+      let result = jokes.saveJokeFor(request.body.jokeId, request.body.userEmail);
+
+      if(result) {
+        response.status(201).end(); // 201: Created
+      }
+    }
+
+    response.status(400).end(); // 400: Bad request
+  });
+
+  
+/*
+* Dislike a joke
+* params userEmail* user's e-mail
+* params jokeId* joke id to dislike
+*/
+app.delete('/api/v1/jokes', function(request, response) {
+
+    console.log('Dislike', request.body);
+
+    if(request.body.userEmail !== undefined && request.body.jokeId !== undefined) {
+
+      let result = jokes.deleteJokeFor(request.body.jokeId, request.body.userEmail);
+
+      if(result) {
+        response.status(202).end(); // 202: Accepted
+      }
+    }
+
+    response.status(400).end(); // 400: Bad request
+  });
+  
+/*
+* Is specific joke liked or not by specific user
+* params userEmail* user's e-mail
+* params jokeId* joke id to retreive
+*/
+app.get('/api/v1/likes/:userEmail/:jokeId', function(request, response) {
+
+    console.log('Get like', request.params);
+
+    if(request.params.userEmail !== undefined && request.params.jokeId !== undefined) {
+
+      let result = jokes.getJokeFor(request.params.jokeId, request.params.userEmail);
+
+      response.status(200).json({ isLiked: result }); // 200: Ok
+    }
+
+    response.status(400).end(); // 400: Bad request
+  })
+  
+app.listen(PORT_NUMBER);
+console.log('Server started on port: ' + PORT_NUMBER);
diff --git a/frontend/index.html b/frontend/index.html
index cc2704faa6e272abec64ed49642792624e3f39b7..2ce52ca52718588fa9929f169c96e3491f5241dd 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -10,38 +10,38 @@
     
     <header>
       <div class="container">
-        <img src="res/img/home.png"></img>
+        <img src="res/img/home.png" alt="logo" />
         <span><h1>Blagues de Chuck Norris</h1></span>
       </div>
     </header>
     
     <section class="container console">
-      <div class="content"><h3>Console de connection</h3><div>
+      <div class="content console-content" id="user-disconnected">
+        <span id="console-error"></span>
+        <input id="user-email" type="email" name="email" placeholder="Entrez votre address e-mail"/>
+        <div>
+          <button id="login-btn">Connexion</button>
+        </div>
+      </div>
+      <div class="content console-content" id="user-connected" style="display: none;">
+        <span id="welcome-user"></span>
+        <div>
+          <button id="logout-btn">Déconnexion</button>
+        </div>
+      </div>
     </section>
     
     <main class="container">
       <nav class="content">
-        <button>Blagues aléatoires</button>
-        <button>Mes blagues préférées</button>
-        <button>Meilleures blagues</button>
-        <button>Nouvelles blagues</button>
+        <button id="random-jokes">Blagues aléatoires</button>
+        <button id="my-jokes" disabled>Mes blagues préférées</button>
+        <button id="best-jokes" disabled>Meilleures blagues</button>
+        <button id="last-jokes">Nouvelles blagues</button>
       </nav>
       
       <section class="content main-content">
-        <h2>Blagues</h2>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
-
+        <h1 id="main-title"></h1>
+        <div id="jokes"></div>
       </section>
     </main>
     
@@ -53,5 +53,6 @@
       </div>
     </footer>
 
+    <script type="module" src="res/js/main.js"></script>
   </body>
 </html>
diff --git a/frontend/res/css/style.css b/frontend/res/css/style.css
index c08cf45ea585f9d50c8a627fffe84c9014a1c042..09b8e43201bd1c1ab9b6fdbafffd04e5cdbca98d 100644
--- a/frontend/res/css/style.css
+++ b/frontend/res/css/style.css
@@ -1,6 +1,7 @@
 :root {
   --container-width: 768px;
   --container-margin: 16px;
+  --content-padding: 16px;
   --navigation-width: 192px;
 
   --header-height: 100px;
@@ -53,6 +54,7 @@ header > .container {
 
 header > .container > img {
   height: 100%;
+  margin: 0 54px;
 }
 
 header > .container > span {
@@ -70,6 +72,18 @@ header > .container h1 {
   font-size: 2em;
 }
 
+@media (max-width: 800px) {
+  header > .container h1 {
+    font-size: 1em;
+  }
+}
+
+@media (max-width: 500px) {
+  header > .container img {
+    display: none;
+  }
+}
+
 /* NAVIGATION */
 
 .console {
@@ -79,7 +93,41 @@ header > .container h1 {
 
 .console h3 {
   margin: 0;
-  padding: 16px;
+  padding: var(--content-padding);
+}
+
+.console-content {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-end;
+  padding: var(--content-padding);
+}
+
+.console-content > input, .console-content > span {
+  min-width: 200px;
+  margin: 0 var(--content-padding);
+}
+
+@media (max-width: 600px) {
+  .console-content {
+    flex-direction: column;
+  }
+  
+  .console-content > input, .console-content button {
+    height: 32px;
+  }
+  
+  .console-content > input {
+    margin: 0;
+  }
+  
+  .console-content button {
+    width: 100%;
+  }
+}
+
+#console-error {
+  color: red;
 }
 
 /* MAIN */
@@ -96,7 +144,7 @@ nav {
   display: flex;
   flex-direction: column;
   margin-bottom: var(--container-margin);
-  padding: 16px;
+  padding: var(--content-padding);
   width: calc(var(--navigation-width) - 32px);
 }
 
@@ -105,6 +153,14 @@ nav {
   width: calc(var(--container-width) - var(--container-margin) - var(--navigation-width) - 40px);
 }
 
+div.joke {
+  padding: 8px var(--content-padding);
+  margin: 8px 0;
+
+  border-radius: 4px;
+  background-color: #eee;
+}
+
 @media (max-width: 800px) {
   main {
     flex-direction: column;
@@ -114,7 +170,7 @@ nav {
   nav {
     flex-direction: row;
     flex-wrap: wrap;
-    padding: 16px;
+    padding: var(--content-padding);
     width: auto;
   }
   
@@ -127,6 +183,16 @@ nav {
   }
 }
 
+@media (max-width: 600px) {
+  nav {
+    flex-direction: column;
+  }
+  
+  nav > button {
+    height: 32px;
+  }
+}
+
 /* FOOTER */
 
 body {
diff --git a/frontend/res/js/display.js b/frontend/res/js/display.js
new file mode 100644
index 0000000000000000000000000000000000000000..aaa78f7e87bccf507ce2a4697d686f200af0944d
--- /dev/null
+++ b/frontend/res/js/display.js
@@ -0,0 +1,132 @@
+import events from './events.js';
+import login from './login.js';
+
+/*
+* Manage changing on display
+*/
+export default {
+
+  init: () => {
+    // Random jokes
+    events.listen(events.RANDOM_JOKES_BTN_CLICKED, () => displayTitle('Blagues aléatoires'));
+    events.listen(events.RANDOM_JOKES_SUCCEED, displayJokes);
+    // Last jokes
+    events.listen(events.LAST_JOKES_BTN_CLICKED, () => displayTitle('Dernières blagues'));
+    events.listen(events.LAST_JOKES_SUCCEED, displayJokes);
+    // Login & logout
+    events.listen(events.LOGIN_SUCCEED, displayUserConnected);
+    events.listen(events.LOGIN_FAILED, displayUserLoginError);
+    events.listen(events.LOGOUT_SUCCEED, displayUserDisconnected);
+    // Like & dislike
+    events.listen([events.DISLIKE_JOKE_SUCCEED, events.LIKE_JOKE_FAILED], activeLikeBtn);
+    events.listen([
+      events.LIKE_JOKE_SUCCEED,
+      events.DISLIKE_JOKE_FAILED,
+      events.RETREIVE_LIKE_SUCCEED
+    ], activeDislikeBtn);
+  }
+}
+
+function displayUserConnected(data) {
+  document.getElementById('console-error').style.display = 'none';
+  document.getElementById('user-disconnected').style.display = 'none';
+  document.getElementById('user-connected').style.display = 'flex';
+  
+  document.getElementById('welcome-user').innerHTML = 'Connecté: ' + data.userEmail;
+  
+  changeDisabledPropertyForAllLikeBtn(false);
+}
+
+function displayUserLoginError(data) {
+  const consoleError = document.getElementById('console-error');
+  consoleError.style.display = 'block';
+  consoleError.innerHTML = 'L\'adresse e-mail n\'est pas valide';
+}
+
+function displayUserDisconnected() {
+  document.getElementById('user-connected').style.display = 'none';
+  document.getElementById('user-disconnected').style.display = 'flex';
+  
+  changeDisabledPropertyForAllLikeBtn(true);
+}
+
+function changeDisabledPropertyForAllLikeBtn(disabled) {
+  let likeBtnElementArray = document.getElementsByClassName('like-btn');
+  for (let likeBtnElement of likeBtnElementArray) {
+    likeBtnElement.disabled = disabled;
+  }
+}
+
+function displayTitle(title) {
+  document.getElementById('jokes').innerHTML = '<p>Chargement... </p>';
+  document.getElementById('main-title').innerHTML = title;
+}
+
+function displayJokes(jokeArray) {
+  emptyJokeContainer();
+
+  for(let joke of jokeArray) {
+    displayJoke(joke);
+  }
+}
+
+function emptyJokeContainer() {
+  document.getElementById('jokes').innerHTML = '';
+}
+
+function displayJoke(data) {
+  let element = createJokeElement(data.id, data.joke);
+  element.title = data.joke;
+  document.getElementById('jokes').appendChild(element);
+}
+
+function createJokeElement(jokeId, text) {
+  let jokeElement = document.createElement('div');
+  jokeElement.className = 'joke';
+  jokeElement.setAttribute('id', 'joke-id-' + jokeId);
+  jokeElement.appendChild(createParagraph(text));
+  jokeElement.appendChild(createLikeBtn(jokeId));
+  return jokeElement;
+}
+
+function createParagraph(jokeText) {
+  let paragraphElement = document.createElement('p');
+  paragraphElement.innerHTML = jokeText;
+  return paragraphElement;
+}
+
+function createLikeBtn(jokeId, liked) {
+  let likeBtnElement = document.createElement('button');
+  likeBtnElement.className = 'like-btn';
+  likeBtnElement.innerHTML = liked ? 'Dislike' : 'Like';
+  likeBtnElement.disabled = !login.isUserConnected();
+  likeBtnElement.onclick = () => sendeventsAndToggleButton(likeBtnElement, jokeId);
+  return likeBtnElement;
+}
+
+function sendeventsAndToggleButton(likeBtnElement, jokeId) {
+  if (likeBtnElement.innerHTML === 'Like') {
+    events.send(events.LIKE_JOKE_BTN_CLICKED, { jokeId });
+  } else {
+    events.send(events.DISLIKE_JOKE_BTN_CLICKED, { jokeId });
+  }
+  likeBtnElement.disabled = !login.isUserConnected();
+}
+
+function activeLikeBtn(data) {
+  const jokeElement = document.getElementById('joke-id-' + data.jokeId);
+  if (jokeElement) {
+    const likeBtnElement = jokeElement.getElementsByClassName('like-btn')[0];
+    likeBtnElement.innerHTML = 'Like';
+    likeBtnElement.disabled = false;
+  }
+}
+
+function activeDislikeBtn(data) {
+  const jokeElement = document.getElementById('joke-id-' + data.jokeId);
+  if (jokeElement) {
+    const dislikeBtnElement = jokeElement.getElementsByClassName('like-btn')[0];
+    dislikeBtnElement.innerHTML = 'Dislike';
+    dislikeBtnElement.disabled = false;
+  }
+}
diff --git a/frontend/res/js/events.js b/frontend/res/js/events.js
new file mode 100644
index 0000000000000000000000000000000000000000..cc717c5ac8304eff572e939c8f8c6bba6f5109c1
--- /dev/null
+++ b/frontend/res/js/events.js
@@ -0,0 +1,57 @@
+/*
+* Manage events listening and subscribing
+*/
+export default {
+  // Login
+  LOGIN_BTN_CLICKED: 'User clicked on login button',
+  LOGIN_SUCCEED: 'User login succeed',
+  LOGIN_FAILED: 'User login failed',
+  // Logout
+  LOGOUT_BTN_CLICKED: 'User clicked on logout button',
+  LOGOUT_SUCCEED: 'User logout succeed',
+  // My jokes
+  MY_JOKES_BTN_CLICKED: 'User clicked on my-jokes button',
+  // Random jokes
+  RANDOM_JOKES_BTN_CLICKED: 'User clicked on random-jokes button',
+  RANDOM_JOKES_SUCCEED: 'Random jokes retreiving from API succeed',
+  RANDOM_JOKES_FAILED: 'Random jokes retreiving from API failed',
+  // Best jokes
+  BEST_JOKES_BTN_CLICKED: 'User clicked on best-jokes button',
+  // Last jokes
+  LAST_JOKES_BTN_CLICKED: 'User clicked on last-jokes button',
+  LAST_JOKES_SUCCEED: 'Last jokes retreiving from API succeed',
+  LAST_JOKES_FAILED: 'Last jokes retreiving from API failed',
+  // Like joke
+  LIKE_JOKE_BTN_CLICKED: 'User clicked on like joke button',
+  LIKE_JOKE_SUCCEED: 'Like joke sending to API succeed',
+  LIKE_JOKE_FAILED: 'Like joke sending to API failed',
+  // Dislike joke
+  DISLIKE_JOKE_BTN_CLICKED: 'User clicked on dislike joke button',
+  DISLIKE_JOKE_SUCCEED: 'Dislike joke sending to API succeed',
+  DISLIKE_JOKE_FAILED: 'Dislike joke sending to API failed',
+  RETREIVE_LIKE_SUCCEED: 'Retreive like for joke id',
+  
+  send: (action, data = null) => {
+    const event = new CustomEvent(action, { detail: data });
+    document.body.dispatchEvent(event);
+    if (data) {
+      console.log(action, data);
+    } else {
+      console.log(action);
+    }
+  },
+  
+  listen: (actionOractionArray, callback) => {
+    if (Array.isArray(actionOractionArray)) {
+      for (const action of actionOractionArray) {
+        listen(action, callback);
+      }
+    } else {
+      listen(actionOractionArray, callback);
+    }
+  }
+}
+
+function listen(action, callback) {
+  document.body.addEventListener(action, e => callback(e.detail));
+}
diff --git a/frontend/res/js/jokes.js b/frontend/res/js/jokes.js
new file mode 100644
index 0000000000000000000000000000000000000000..9c73d3c4bf8becb1d73614a678ff67a14d92638d
--- /dev/null
+++ b/frontend/res/js/jokes.js
@@ -0,0 +1,110 @@
+import events from './events.js';
+import login from './login.js';
+
+const ICNDB_API = 'https://api.icndb.com';
+const ICNDB_DEFAULT_ARGUMENT = '?exclude=[explicit]';
+
+const JOKES_LIMIT = 10;
+
+/*
+* Manage jokes API
+*/
+export default {
+
+  init: () => {
+    events.listen([events.RANDOM_JOKES_BTN_CLICKED, events.LOGIN_SUCCEED], retreiveRandomJokes);
+    events.listen(events.LAST_JOKES_BTN_CLICKED, retreiveLastJokes);
+    events.listen(events.LIKE_JOKE_BTN_CLICKED, likeJoke);
+    events.listen(events.DISLIKE_JOKE_BTN_CLICKED, dislikeJoke);
+    events.listen([events.RANDOM_JOKES_SUCCEED, events.LAST_JOKES_SUCCEED], checkLike);
+  },
+}
+
+function retreiveRandomJokes() {
+  return fetch(ICNDB_API + '/jokes/random/' + JOKES_LIMIT + ICNDB_DEFAULT_ARGUMENT)
+    .then(response => response.json())
+    .then(dataJSON => {
+      if (dataJSON.type === 'success') {
+        events.send(events.RANDOM_JOKES_SUCCEED, dataJSON.value);
+      } else {
+        events.send(events.RANDOM_JOKES_FAILED, dataJSON.error);
+      }
+    })
+}
+
+function retreiveLastJokes() {
+  return fetch(ICNDB_API + '/jokes' + ICNDB_DEFAULT_ARGUMENT)
+    .then(response => response.json())
+    .then(dataJSON => {
+      if (dataJSON.type === 'success') {
+        let lastJokes = filterLastJokes(dataJSON.value, JOKES_LIMIT);
+        events.send(events.LAST_JOKES_SUCCEED, lastJokes);
+      } else {
+        events.send(events.LAST_JOKES_FAILED, dataJSON.error);
+      }
+    })
+}
+
+function filterLastJokes(jokes, nbr) {
+  return jokes.slice(Math.max(jokes.length - nbr, 1));
+}
+
+function likeJoke(data) {
+  if(login.isUserConnected()) {
+    data.userEmail = login.getConnectedUserEmail();
+    const config = getConfigJSON(data, 'POST');
+
+    return fetch('//localhost:8080/api/v1/jokes', config)
+      .then(function(response) {
+        if(response.status === 201) {
+          events.send(events.LIKE_JOKE_SUCCEED, data);
+        } else {
+          events.send(events.LIKE_JOKE_FAILED, response.error);
+        }
+      });
+  }
+  events.send(LIKE_JOKE_FAILED, 'User not connected');
+}
+
+function dislikeJoke(data) {
+  if(login.isUserConnected()) {
+    data.userEmail = login.getConnectedUserEmail();
+    const config = getConfigJSON(data, 'DELETE');
+
+    return fetch('//localhost:8080/api/v1/jokes', config)
+      .then(function(response) {
+        if(response.status === 202) {
+          events.send(events.DISLIKE_JOKE_SUCCEED, data);
+        } else {
+          events.send(events.DISLIKE_JOKE_FAILED, response.error);
+        }
+      });
+  }
+  events.send(DISLIKE_JOKE_FAILED, 'User not connected');
+}
+
+function checkLike(data) {
+  if(login.isUserConnected()) {
+    const userEmail = login.getConnectedUserEmail();
+    data.forEach(joke => {
+      return fetch('//localhost:8080/api/v1/likes/' + userEmail + '/' + joke.id)
+        .then(response => response.json())
+        .then(result => {
+          if(result.isLiked) {
+            events.send(events.RETREIVE_LIKE_SUCCEED, { jokeId: joke.id});
+          }
+        });
+    });
+  }
+}
+
+function getConfigJSON(data, method = 'GET') {
+  return {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json'
+    },
+    method: method,
+    body: JSON.stringify(data)
+  }
+}
diff --git a/frontend/res/js/login.js b/frontend/res/js/login.js
new file mode 100644
index 0000000000000000000000000000000000000000..3a7588160a4fb40af89d4f57f0a04523ae82746f
--- /dev/null
+++ b/frontend/res/js/login.js
@@ -0,0 +1,42 @@
+import events from './events.js';
+
+let userEmail;
+
+/*
+* Manage login and logout user only in local
+*/
+export default {
+  
+  init: () => {
+    events.listen(events.LOGIN_BTN_CLICKED, login);
+    events.listen(events.LOGOUT_BTN_CLICKED, logout);
+  },
+
+  isUserConnected: () => userEmail !== undefined,
+
+  getConnectedUserEmail: () => userEmail,
+}
+
+function login(data) {
+  console.log('Connecting...');
+
+  if (validateEmail(data.userEmail)) {
+    userEmail = data.userEmail;
+    events.send(events.LOGIN_SUCCEED, data);
+  } else {
+    // TODO Display error
+    events.send(events.LOGIN_FAILED);
+  }
+  
+}
+
+function logout() {
+  console.log('Disconnecting...');
+  userEmail = undefined;
+  events.send(events.LOGOUT_SUCCEED);
+}
+
+function validateEmail(email) {
+  var re = /\S+@\S+\.\S+/;
+  return re.test(email);
+}
diff --git a/frontend/res/js/main.js b/frontend/res/js/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..115f67ea06fd8c35d7cc5d40c0758a241ff11479
--- /dev/null
+++ b/frontend/res/js/main.js
@@ -0,0 +1,23 @@
+import display from './display.js';
+import events from './events.js';
+import jokes from './jokes.js';
+import login from './login.js';
+
+// Initialization
+display.init();
+jokes.init();
+login.init();
+
+document.getElementById('login-btn').onclick = () => {
+  const userEmailElement = document.getElementById('user-email');
+  events.send(events.LOGIN_BTN_CLICKED, { userEmail: userEmailElement.value })
+}
+document.getElementById('logout-btn').onclick = () => events.send(events.LOGOUT_BTN_CLICKED);
+
+document.getElementById('my-jokes').onclick = () => events.send(events.MY_JOKES_BTN_CLICKED);
+document.getElementById('random-jokes').onclick = () => events.send(events.RANDOM_JOKES_BTN_CLICKED);
+document.getElementById('best-jokes').onclick = () => events.send(events.BEST_JOKES_BTN_CLICKED);
+document.getElementById('last-jokes').onclick = () => events.send(events.LAST_JOKES_BTN_CLICKED);
+
+// Display random jokes by default
+events.send(events.RANDOM_JOKES_BTN_CLICKED);