Skip to content

15. Example [nuxt-12]: HTTP requests with axios

15.1. Introduction

In this new example, we will explore how to make HTTP requests using the [axios] library within [asyncData] functions. Additionally, we will apply concepts already covered:

  • the use of plugins from the [nuxt-06] example:
  • storing the store in a session cookie from the [nuxt-06] example;
  • navigation control using middleware from the [nuxt-09] example;
  • error handling from the [nuxt-11] example;

The architecture of the example will be as follows:

Image

  • The [nuxt] application will be hosted on the [node.js] server [3], downloaded by the browser [1], which will then execute it;
  • both the [nuxt] client [1] and the [nuxt] server [3] will make HTTP requests to the data server [2]. This server will be the tax calculation server developed in the PHP 7 section. We will use its latest version, version 14, with CORS requests enabled;

The architecture of the example can be simplified as follows:

Image

  • In [1], the [node.js] server delivers the [nuxt] pages to the browser [2]. It is the [web] layer [8] of the server that delivers these pages. To deliver the page, the server may have requested external data from the data server [3]. It is the [DAO] layer [9] that makes the necessary HTTP requests;
  • With each page request to the [Node.js] server [1], the browser [2] receives the entire [Nuxt] application, which then runs in SPA mode. The [UI] (User Interface) block [4] displays [Vue.js] pages to the user. The user’s actions or the natural lifecycle of the pages can trigger requests for external data from the data server [3]. The [DAO] layer [5] then makes the necessary HTTP requests;

15.2. Project directory structure

Image

15.3. The [nuxt.config.js] configuration file

The project will be controlled by the following [nuxt.config.js] file:


export default {
  mode: 'universal',
  /*
   ** Headers of the page
   */
  head: {
    title: 'Introduction à [nuxt.js]',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: 'ssr routing loading asyncdata middleware plugins store'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: false,
 
  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [
    { src: '@/plugins/client/plgSession', mode: 'client' },
    { src: '@/plugins/server/plgSession', mode: 'server' },
    { src: '@/plugins/client/plgDao', mode: 'client' },
    { src: '@/plugins/server/plgDao', mode: 'server' },
    { src: '@/plugins/client/plgEventBus', mode: 'client' }
  ],
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Doc: https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    // https://www.npmjs.com/package/cookie-universal-nuxt
    'cookie-universal-nuxt'
  ],
  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {},
  /*
   ** Build configuration
   */
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) { }
  },
  // source code directory
  srcDir: 'nuxt-12',
  // router
  router: {
    // application URL root
    base: '/nuxt-12/',
    // routing middleware
    middleware: ['routing']
  },
  // server
  server: {
    // service port, default 3000
    port: 81,
    // network addresses listened to, default localhost: 127.0.0.1
    // 0.0.0.0 = all the machine's network addresses
    host: 'localhost'
  },
  // environment
  env: {
    // axios configuration
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // session cookie configuration [nuxt]
    maxAge: 60 * 5
  }
}
  • line 22: we handle the notification for the completion of an asynchronous action ourselves;
  • line 31: we will use various plugins that are specialized either for the client or for the server, but not for both at the same time;
  • line 52: the [axios] module is integrated into [nuxt]. As a result, the [axios] object, which will handle HTTP requests from the [nuxt] application to the PHP tax calculation server, will be available in [context.$axios];
  • line 54: the [cookie-universal-nuxt] module will allow us to save the [nuxt] session in a cookie;
  • line 60: the [axios] property allows us to configure the [@nuxtjs/axios] module from line 52. We will not use this option, preferring instead the [env] property from line 88;
  • line 90: maximum wait time for a response from the tax calculation server;
  • line 91: required for the [nuxt] client—enables the use of cookies in communications with the tax calculation server;
  • line 92: the base URL of the tax calculation server;
  • line 94: Nuxt session lifetime (5 min);
  • line 77: client and [nuxt] server navigation will be controlled by routing middleware;

15.4. The [UI] layer of the application

Image

We will give the [nuxt] application access to the tax calculation server’s API via the following view:

Image

  • in [2], the menu providing access to the tax calculation server’s API:
    • [Authentication]: corresponds to the [authentication] page. This page sends an authentication request to the tax calculation server using the credentials [admin, admin], which are currently the only authorized ones. The displayed result is similar to [3];
    • [AdminData Request]: corresponds to the [get-admindata] page. This page requests data from the tax calculation server—referred to here as [adminData]—which enables tax calculation. The displayed result is similar to [3];
    • [End Tax Session]: corresponds to the [end-session] page. This page sends a PHP end-of-session request to the tax calculation server. The server then cancels the current PHP session and initializes a new, blank one;

15.5. The [dao] layers of the [nuxt] application

As mentioned above, the architecture of the [nuxt] application will be as follows:

Image

  • In [1], the [node.js] server delivers the [nuxt] pages to the browser [2]. It is the server’s [web] layer [8] that delivers these pages. To deliver the page, the server may have requested external data from the data server [3]. It is the [DAO] layer [9] that makes the necessary HTTP requests;
  • With each page request to the [Node.js] server [1], the browser [2] receives the entire [Nuxt] application, which then runs in SPA mode. The [UI] (User Interface) block [4] displays [Vue.js] pages to the user. User actions or the page lifecycle may trigger requests for external data from the data server [3]. The [DAO] layer [5] then makes the necessary HTTP requests;

We will use version 14 of the tax calculation server developed in the document |Introduction to PHP7 through Examples|. We will use only part of its JSON API (Application Programming Interface):

Request
Response
1
2
3
4
5
6
[initialization of a JSON session with the
tax calculation server]
[a PHP session is created with the
tax calculation server]

GET main.php?action=init-session&type=json

{
    "action": "init-session",
    "status": 700,
    "response": "session started with type [json]"
}

[user authentication]
[authentication is stored in the
PHP session]
 
POST main.php?action=authenticate-user
posted parameters: user, admin

{
    "action": "authenticate-user",
    "status": 200,
    "response": "Authentication successful [admin, admin]"
}

[request for tax administration data
data]
[the received data is stored in the
PHP session]
 
GET main.php?action=get-admindata

{
    "action": "get-admindata",
    "status": 1000,
    "response": {
        "limits": [
            9964,
            27519,
            73779,
            156244,
            0
        ],
        "coeffR": [
            0,
            0.14,
            0.3,
            0.41,
            0.45
        ],
        "coeffN": [
            0,
            1394.96,
            5798,
            13913.69,
            20163.45
        ],
        "half-share-income-limit": 1551,
        "IncomeLimitSingleForReduction": 21037,
        "coupleIncomeLimitForReduction": 42074,
        "half-share-reduction-value": 3797,
        "singleDiscountLimit": 1196,
        "coupleDiscountLimit": 1970,
        "coupleTaxCeilingForDiscount": 2627,
        "singleTaxCeilingForDiscount": 1595,
        "MaxTenPercentDeduction": 12502,
        "10%MinDiscount": 437
    }
}

[end of PHP session with the
tax calculation]
[deletes the current PHP session and creates a
new PHP session. In this session, the
JSON session remains active, but the user
is no longer authenticated]
 
GET main.php?action=end-session

{
    "action": "end-session",
    "status": 400,
    "response": "session deleted"
}

15.5.1. The [DAO] layer of the [Nuxt] server

Image

The [node.js] server [1] will use the [DAO] layer described in the document |Introduction to the VUE.JS framework through examples|. Here is the code again:


'use strict';
 
// imports
import qs from 'qs'
 
class Dao {
 
  // manufacturer
  constructor(axios) {
    this.axios = axios;
    // session cookie
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
  }
 
  // init session
  async  initSession() {
    // query options HHTP [get /main.php?action=init-session&type=json]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: 'init-session',
        type: 'json'
      }
    };
    // execute query HTTP
    return await this.getRemoteData(options);
  }
 
  async  authentifierUtilisateur(user, password) {
    // query options HHTP [post /main.php?action=authenticate-user]
    const options = {
      method: "POST",
      headers: {
        'Content-type': 'application/x-www-form-urlencoded',
      },
      // body of POST
      data: qs.stringify({
        user: user,
        password: password
      }),
      // URL parameters
      params: {
        action: 'authentifier-utilisateur'
      }
    };
    // execute query HTTP
    return await this.getRemoteData(options);
  }
 
  async getAdminData() {
    // query options HHTP [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: 'get-admindata'
      }
    };
    // execute query HTTP
    const data = await this.getRemoteData(options);
    // result
    return data;
  }
 
  async  getRemoteData(options) {
    // for the session cookie
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // execute query HTTP
    let response;
    try {
      // asynchronous request
      response = await this.axios.request('main.php', options);
    } catch (error) {
      // the [error] parameter is an exception instance - it can take various forms
      if (error.response) {
        // the server response is in [error.response]
        response = error.response;
      } else {
        // error restart
        throw error;
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + response itself)
    // retrieve the session cookie if it exists
    const setCookie = response.headers['set-cookie'];
    if (setCookie) {
      // setCookie is an array
      // look for the session cookie in this table
      let trouvé = false;
      let i = 0;
      while (!trouvé && i < setCookie.length) {
        // look for the session cookie
        const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // the session cookie is stored
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // we found
          trouvé = true;
        } else {
          // next item
          i++;
        }
      }
    }
    // the server response is in [response.data]
    return response.data;
  }
}
 
// class export
export default Dao;
  • All methods in the [dao] layer return the object sent by the data server [{action: ‘xx’, status: nn, response: {...}] with:
    • [action]: the name of the action executed by the data server;
    • [state]: numeric indicator:
      • [initSession]: status=700 for a response with no errors;
      • [authenticateUser]: status=200 for a response with no errors;
      • [getAdminData]: status=1000 for a response with no errors;
      • [end-session]: status=400 for a response with no errors;
    • [response]: response associated with the numeric indicator [status]. May vary depending on this numeric indicator;

Let’s examine the constructor of the [Dao] class:


// constructeur
  constructor(axios) {
    this.axios = axios;
    // cookie de session
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
}
  • line 2: the [axios] object passed as an argument to the constructor is provided by the calling code. It is this object that will make the HTTP requests;
  • line 5: the name of the session cookie sent by the data server written in PHP;
  • line 6: the session cookie exchanged between the [dao] layer and the data server. This is initialized by the [getRemoteData] function in lines 67–113;

For the session cookie, we need to consider two separate [dao] layers:

  • the browser layer;
  • the server layer;

We will need to manage three session cookies:

  1. the one exchanged between the [nuxt] client and the PHP 7 server;
  2. the one exchanged between the [nuxt] server and the PHP 7 server;
  3. the one exchanged between the client [nuxt] and the [nuxt] server;

We will ensure that the session cookie with the PHP server is the same for both the client and the [nuxt] server. We will call this cookie the PHP session cookie. This cookie is the one from cases 1 and 2. We will call the cookie from case 3 the [nuxt] session cookie. We will therefore have two sessions:

  • a PHP session with the PHP session cookie;
  • a [nuxt] session with the [nuxt] session cookie;

Why use the same cookie for the client’s PHP sessions and the [nuxt] browser’s sessions? We want the application to be able to communicate with the PHP 7 server regardless of whether it’s the client or the [nuxt] server:

  • if an action A from the [nuxt] server puts the PHP server into state E, this state is reflected in the PHP session maintained by the PHP server;
  • by using the same PHP session cookie as the server, a client action B [nuxt] following server action A [nuxt] would find the PHP server in state E left by the server [nuxt] and could therefore build on the work already done by the server [nuxt];
  • if, after action B from the [nuxt] client, an action C from the [nuxt] server follows, for the same reason as before, this action will be able to build on the work done by action B from the [nuxt] client;

To enable the [nuxt] client’s browser to communicate with the tax calculation PHP server, we will use version 14 of this server, which allows cross-domain calls—i.e., calls from a browser to the PHP server. Calls from the [nuxt] server to the PHP server, however, are not cross-domain calls. This concept applies only to calls made from a browser.

Let’s return to the constructor code of the previous [Dao] class:


// constructeur
  constructor(axios) {
    this.axios = axios;
    // cookie de session
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
}
  • Lines 5 and 6 correspond to the PHP session cookie with the tax calculation server;

The management of the PHP session cookie above is not suitable for the [nuxt] server: its [dao] layer is instantiated with each new request made to the [nuxt] server. Recall that requesting a page from the [nuxt] server effectively resets the [nuxt] application. Thus, when the [nuxt] server makes its first request to the data server, the PHP session cookie for the [dao] layer is initialized, this value is lost during the next HTTP request from the same [nuxt] server, because in the meantime its [dao] layer has been recreated, the constructor re-executed, and the PHP session cookie reset to an empty string (line 6);

One solution is to use a different constructor for the server’s [dao] layer:


// constructeur
  constructor(axios, phpSessionCookie) {
    // bibliothèque axios
    this.axios = axios
    // valeur du cookie de session
    this.phpSessionCookie = phpSessionCookie
    // nom du cookie de session du serveur PHP
    this.phpSessionCookieName = 'PHPSESSID'
  }
  • line 2: this time, the PHP session cookie will be provided to the [DAO] layer constructor of the data server;

How will the [nuxt] server provide this PHP session cookie to the constructor of its [dao] layer? We will store the PHP session cookie in the [nuxt] session cookie exchanged between the browser and the [nuxt] server. The process is as follows:

  1. The [nuxt] application is launched;
  2. when the [nuxt] server makes its first HTTP request to the PHP server, it stores the PHP session cookie it received in the [nuxt] session cookie that it exchanges with the [nuxt] client;
  3. The browser hosting the [nuxt] client receives this [nuxt] session cookie and therefore automatically sends it back with every new request to the [nuxt] server;
  4. when the [nuxt] server needs to make a new request to the PHP server, it will find the PHP session cookie within the [nuxt] session cookie that the browser has sent it. It will then send it to the PHP server;

There are indeed two session cookies, and they must not be confused:

  • the [nuxt] session cookie exchanged between the [nuxt] server and the [nuxt] client’s browser;
  • the PHP session cookie exchanged between the [nuxt] server and the PHP server or between the [nuxt] client and the PHP server;

Let’s now return to the code for the [Dao] class method. It does not include a function to close the PHP session with the tax calculation server. We’ll add this one:


// end of tax calculation session
  async finSession() {
    // query options HHTP [get /main.php?action=end-session]
    const options = {
      method: 'GET',
      // URL parameters
      params: {
        action: 'fin-session'
      }
    }
    // execute query HTTP
    const data = await this.getRemoteData(options)
    // result
    return data
  }

During testing, we discover that the [getRemoteData] function called on line 12 is not suitable for the [finSession] method:


async  getRemoteData(options) {
    // for the session cookie
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // execute query HTTP
    let response;
    try {
      // asynchronous request
      response = await this.axios.request('main.php', options);
    } catch (error) {
      // the [error] parameter is an exception instance - it can take various forms
      if (error.response) {
        // the server response is in [error.response]
        response = error.response;
      } else {
        // error restart
        throw error;
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + response itself)
    // retrieve the session cookie if it exists
    const setCookie = response.headers['set-cookie'];
    if (setCookie) {
      // setCookie is an array
      // look for the session cookie in this table
      let trouvé = false;
      let i = 0;
      while (!trouvé && i < setCookie.length) {
        // look for the session cookie
        const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // the session cookie is stored
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // we found
          trouvé = true;
        } else {
          // next item
          i++;
        }
      }
    }
    // the server response is in [response.data]
    return response.data;
  }
  • lines 30–43: we search for the cookie [PHPSESSID=xxx]. If found, it is stored in the class (line 36);

This code is not suitable for the new [finSession] method because, on the [fin-session] action, the PHP server sends two cookies named [PHPSESSID]. Here is an example obtained using a [Postman] client:

Image

  • in [1], the request from the [Postman] client;
  • in [3], the PHP server’s response;
  • in [4], the HTTP headers of the PHP server’s response;

Image

  • in [5], the PHP server first indicates that it has deleted the current PHP session;
  • in [6], the PHP server sends the cookie for the new PHP session;

With the current code, the [getRemoteData] function retrieves cookie [5], whereas it is cookie [6] that needs to be stored.

We must therefore update the code for the [getRemoteData] function:


async getRemoteData(options) {
    // is there a PHP session cookie?
    if (this.phpSessionCookie) {
      // are there headers?
      if (!options.headers) {
        // create an empty object
        options.headers = {}
      }
      // session cookie header PHP
      options.headers.Cookie = this.phpSessionCookie
    }
    // execute query HTTP
    let response
    try {
      // asynchronous request
      response = await this.axios.request('main.php', options)
    } catch (error) {
      // the [error] parameter is an exception instance - it can take various forms
      if (error.response) {
        // the server response is in [error.response]
        response = error.response
      } else {
        // error restart
        throw error
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + response itself)
    // look for session cookie PHP in received cookies
    // all cookies received
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // cookies is a picture
      // look for the PHP session cookie in this array
      let trouvé = false
      let i = 0
      while (!trouvé && i < cookies.length) {
        // look for the PHP session cookie
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // we store the PHP session cookie
          const phpSessionCookie = results[1]
          // is the word [deleted] in it?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // we have the right session cookie PHP
            this.phpSessionCookie = phpSessionCookie
            // we found
            trouvé = true
          } else {
            // next item
            i++
          }
        } else {
          // next item
          i++
        }
      }
    }
    // the server response is in [response.data]
    return response.data
  }
  • Line 41: We found a cookie named [PHPSESSID]. We store it locally;
  • line 43: we check if the saved cookie contains the string [PHPSESSID=deleted];
  • line 46: if the answer is no, then we have found the correct cookie [PHPSESSID]. We store it in the class;

After the [getRemoteData] function, the PHP session cookie is stored in the class, in [this.phpSessionCookie]. We mentioned that the class is instantiated with each new HTTP request from the [nuxt] server. The PHP session cookie must therefore be exfiltrated from the class. To do this, we add a new method to it:


// accès au cookie de la session PHP
  getPhpSessionCookie() {
    return this.phpSessionCookie
}
  • The [nuxt] server requests an action from its [dao] layer by providing the PHP session cookie to its constructor, if it has one;
  • Once the action is complete, the [nuxt] server retrieves the PHP session cookie stored by the [dao] layer using the previous [getPhpSessionCookie] method. This cookie may be the same as the previous one or a different one. The latter case occurs on two occasions:
    • when the [initSession] method is executed (there was no PHP session cookie before);
    • when the [finSession] method is executed (the PHP server changes the PHP session cookie);

Note a peculiarity regarding the PHP session cookie. The [nuxt] server does not always receive this cookie from the PHP server. In fact, the PHP server sends it only once. After that, it no longer sends it. When looking at the code for [getRemoteData] and [getPhpSessionCookie], we can see that when the PHP server does not send a session cookie, the [getPhpSessionCookie] function returns the PHP session cookie provided to the constructor. This is how the server always sends the PHP server the last PHP session cookie that the PHP server sent to it.

15.5.2. The [dao] layer of the [nuxt] client

Image

For the [nuxt] client running in a browser, we use the code from the [Dao] class in the document |Introduction to the VUE.JS framework through examples|:


"use strict";
 
// imports
import qs from "qs";
 
class Dao {
  // manufacturer
  constructor(axios) {
    this.axios = axios;
  }
 
  // init session
  async initSession() {
    // query options HHTP [get /main.php?action=init-session&type=json]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: "init-session",
        type: "json"
      }
    };
    // execute query HTTP
    return await this.getRemoteData(options);
  }
 
  async authentifierUtilisateur(user, password) {
    // query options HHTP [post /main.php?action=authenticate-user]
    const options = {
      method: "POST",
      headers: {
        "Content-type": "application/x-www-form-urlencoded"
      },
      // body of POST
      data: qs.stringify({
        user: user,
        password: password
      }),
      // URL parameters
      params: {
        action: "authentifier-utilisateur"
      }
    };
    // execute query HTTP
    return await this.getRemoteData(options);
  }
 
  async getAdminData() {
    // query options HHTP [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: "get-admindata"
      }
    };
    // execute query HTTP
    const data = await this.getRemoteData(options);
    // result
    return data;
  }
 
  async getRemoteData(options) {
    // execute query HTTP
    let response;
    try {
      // asynchronous request
      response = await this.axios.request("main.php", options);
    } catch (error) {
      // the [error] parameter is an exception instance - it can take various forms
      if (error.response) {
        // the server response is in [error.response]
        response = error.response;
      } else {
        // error restart
        throw error;
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + response itself)
    // the server response is in [response.data]
    return response.data;
  }
}
 
// class export
export default Dao;

This code differs from the [dao] layer of the [nuxt] server in that it does not manage the PHP session cookie with the tax calculation server: the browser handles that.

We will, as we did for the [dao] layer of the [nuxt] server, add a [finSession] method:


// end of tax calculation session
  async finSession() {
    // query options HHTP [get /main.php?action=end-session]
    const options = {
      method: 'GET',
      // URL parameters
      params: {
        action: 'fin-session'
      }
    }
    // execute query HTTP
    const data = await this.getRemoteData(options)
    // result
    return data
  }

When the [nuxt] client executes this method, it receives, just like the [nuxt] server, two PHP session cookies. It is actually the browser that receives them, and it handles the situation correctly: it keeps only the cookie for the new PHP session initiated by the tax calculation server. So, the next time the [nuxt] client sends a request to the PHP server, the PHP session cookie will be correct because the browser is the one sending it. However, there is a problem: the [nuxt] server is unaware that the PHP session cookie has changed. In its communications with the PHP server, it will then send a PHP session cookie that no longer exists, and this will cause issues. The [nuxt] client needs to notify the [nuxt] server that the PHP session cookie has changed and pass it on to the server. We know how it can do this: via the [nuxt] session cookie, the cookie exchanged between the client and the [nuxt] server. The [nuxt] client has at least two ways to retrieve the new PHP session cookie:

  1. by requesting it from the browser;
  2. by using the [getRemoteData] method of the server, which knows how to retrieve the new PHP session cookie;

We’ll use the second solution because it’s already ready to go. The [getRemoteData] method of the [nuxt] client then becomes the following:


async getRemoteData(options) {
    // execute query HTTP
    let response
    try {
      // asynchronous request
      response = await this.axios.request('main.php', options)
    } catch (error) {
      // the [error] parameter is an exception instance - it can take various forms
      if (error.response) {
        // the server response is in [error.response]
        response = error.response
      } else {
        // error restart
        throw error
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + response itself)
    // look for session cookie PHP in received cookies
    // all cookies received
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // cookies is a picture
      // look for session cookie PHP in this array
      let trouvé = false
      let i = 0
      while (!trouvé && i < cookies.length) {
        // look for the PHP session cookie
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // we store the PHP session cookie
          const phpSessionCookie = results[1]
          // is the word [deleted] in it?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // we have the right session cookie PHP
            this.phpSessionCookie = phpSessionCookie
            // we found
            trouvé = true
          } else {
            // next item
            i++
          }
        } else {
          // next item
          i++
        }
      }
    }
    // the server response is in [response.data]
    return response.data
  }

We kept only the code in [getRemoteData] that processes the PHP server’s response to retrieve the PHP session cookie. We did not keep the code that included the PHP session cookie in the request to the PHP server because the browser hosting the [nuxt] client handles that.

Once the PHP session cookie is obtained by the [nuxt] client, it must be placed in the [nuxt] session so that the [nuxt] server can use it. The [dao] layer does not handle this, but it provides access via a method to the PHP session cookie it has stored:


// accès au cookie de la session PHP
  getPhpSessionCookie() {
    return this.phpSessionCookie
}

The [getPhpSessionCookie] function does not always return a valid session cookie:

  • it is important to remember here that the [dao] layer of the [nuxt] client is persistent. It is instantiated once and then remains in memory;
  • as long as the PHP server does not send a PHP session cookie to the [nuxt] client, the [getPhpSessionCookie] function of the [nuxt] client returns a [undefined] value;
  • when the PHP server sends a PHP session cookie to the [nuxt] client, it is stored in [this.phpSessionCookie] and will remain there until it is replaced by a new PHP session cookie sent by the PHP server. The [getPhpSessionCookie] function of the [nuxt] client then returns the last PHP session cookie received;

The [dao] layer of the [nuxt] client differs from that of the [nuxt] server in only one respect: it does not send the PHP session cookie itself, as the browser handles this. Nevertheless, we chose to maintain two distinct [dao] layers because the reasoning behind their respective implementations differs.

15.6. The [nuxt] session

Image

The [nuxt] session (between the client and the Nuxt server) will be encapsulated in the following [session] object:


/* eslint-disable no-console */
// définition de la session
const session = {
  // contenu de la session
  value: {
    // store non initialisé
    initStoreDone: false,
    // valeur du store Vuex
    store: ''
  },
  // sauvegarde de la session dans un cookie
  save(context) {
    // sauvegarde du store en session
    this.value.store = context.store.state
    console.log('nuxt-session save=', this.value)
    // sauvegarde de la valeur de la session
    context.app.$cookies.set('nuxt-session', this.value, { path: context.base, maxAge: context.env.maxAge })
  },
  // reset de la session
  reset(context) {
    console.log('nuxt-session reset')
    // reset du store
    context.store.commit('reset')
    // sauvegarde du nouveau store en session et sauvegarde de la session
    this.save(context)
  }
}
// export de la session
export default session
  • lines 5–10: the session has only one property [value] with two subproperties:
    • [initStoreDone], which indicates whether the store has been initialized or not;
    • [store]: the [store.state] value of the application’s Vuex store;
  • lines 12–18: the [save] method is used to save the [nuxt] session in a cookie. Here, we use the [cookie-universal-nuxt] library to manage the cookie. Note the name of the [nuxt] session cookie: [nuxt-session] (line 17);
  • lines 20–26: the [reset] method resets the [nuxt] session;
    • line 23: the Vuex store is reset and then saved to the session on line 25;

15.7. [nuxt] session management plugins

Image

15.7.1. The [nuxt] session management plugin for the [nuxt] server

When the application starts, the [nuxt] server runs first. It is therefore responsible for initializing the [nuxt] session. The [server/plgSession] script is as follows:


/* eslint-disable no-console */
 
// import de la session
import session from '@/entities/session'
 
export default (context, inject) => {
  // gestion de la session serveur
  console.log('[plugin server plgSession]')
 
  // y-a-t-il une session existante ?
  const value = context.app.$cookies.get('nuxt-session')
  if (!value) {
    // nouvelle session
    console.log("[plugin server plgSession], démarrage d'une nouvelle session")
  } else {
    // session existante
    console.log("[plugin server plgSession], reprise d'une session existante")
    session.value = value
  }
 
  // on injecte une fonction dans [context, Vue] qui rendra la session courante
  inject('session', () => session)
}
  • line 4: import the [nuxt] session code;
  • line 11: retrieve the value of the [nuxt] session cookie;
  • lines 12–15: If the [nuxt] session cookie did not exist, then the [nuxt] session imported on line 4 is sufficient. There is nothing else to do;
  • lines 15–19: If the [nuxt] session cookie existed, then on line 18 we store its value in the session imported on line 4;
  • line 22: the session has been either initialized or restored. We make it available via the [$session] function;

15.7.2. The [nuxt] session management plugin for the [nuxt] client

The [client/plgSession] script is as follows:


/* eslint-disable no-console */
 
// import de la session
import session from '@/entities/session'
 
export default (context, inject) => {
  // gestion de la session client
  console.log('[plugin client plgSession], reprise de la session [nuxt] du serveur')
  // on récupère la session existante du serveur nuxt
  session.value = context.app.$cookies.get('nuxt-session')
 
  // on injecte une fonction dans [context, Vue] qui rendra la session courante
  inject('session', () => session)
}
  • line 4: the [nuxt] session is imported;
  • line 10: we retrieve the current [nuxt] session from the [nuxt-session] cookie;
  • line 13: we return the [nuxt] session imported on line 4 via the injected function [$session];

15.8. Plugins for the [dao] layers

Image

15.8.1. The [dao] layer plugin for the [nuxt] client

The [client/plgDao] script is as follows:


/* eslint-disable no-console */
// on crée un point d'accès à la couche [Dao]
import Dao from '@/api/client/Dao'
export default (context, inject) => {
  // configuration axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  context.$axios.defaults.withCredentials = context.env.withCredentials
  // instanciation de la couche [dao]
  const dao = new Dao(context.$axios)
  // injection d'une fonction [$dao] dans le contexte
  inject('dao', () => dao)
  // log
  console.log('[fonction client $dao créée]')
}
  • line 3: the [dao] layer of the [nuxt] client is imported;
  • lines 6-8: we configure the [context.$axios] object, which will make HTTP requests for the [dao] layer of the [nuxt] client using information from the [nuxt.config] file:

// environnement
  env: {
    // configuration axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // configuration du cookie de session [nuxt]
    maxAge: 60 * 5
  }
  • line 10: the [dao] layer of the [nuxt] client is instantiated;
  • line 12: the [$dao] function is injected into the client’s context and pages. This function provides access to the [dao] layer from line 10;

So, to access the [dao] layer of the [nuxt] client when it is running, we write:

  • [context.app.$dao()] where the context is known;
  • [this.$dao()] in a [Vue.js] page;

15.8.2. The [dao] layer plugin for the [nuxt] server

The [server/plgDao] script is as follows:


/* eslint-disable no-console */
// on crée un point d'accès à la couche [Dao]
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // configuration axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // on récupère le cookie de session
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // instanciation de la couche [dao]
  const dao = new Dao(context.$axios, phpSessionCookie)
  // injection d'une fonction [$dao] dans le contexte
  inject('dao', () => dao)
  // log
  console.log('[fonction server $dao créée]')
}
  • line 3: the [dao] layer of the [nuxt] server is imported;
  • lines 6-7: the [context.$axios] object is configured to make HTTP requests for the [dao] layer of the [nuxt] server using information from the [nuxt.config] file:

// environnement
  env: {
    // configuration axios
    timeout: 2000,
    withCredentials: true,
    baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
    // configuration du cookie de session [nuxt]
    maxAge: 60 * 5
  }
  • line 9: retrieve the application store [nuxt];
  • line 10: if the store exists, we retrieve the PHP session cookie because we need it to instantiate the [dao] layer of the [nuxt] server;
  • line 13: instantiate the [dao] layer of the [nuxt] server;
  • line 15: the [$dao] function is injected into the context and pages of the [nuxt] server. This function provides access to the [dao] layer from line 13;

So, to access the [dao] layer of the [nuxt] server when it is running, we write:

  • [context.app.$dao()] where the context is known;
  • [this.$dao()] in a [Vue.js] page;

15.9. The Vuex store

Image

The [Vuex] store will store all data that needs to be shared by the various components of the application [pages, client, server] without this data being reactive.


/* eslint-disable no-console */
 
// awning status
export const state = () => ({
  // session jSON started
  jsonSessionStarted: false,
  // authenticated user
  userAuthenticated: false,
  // session cookie PHP
  phpSessionCookie: '',
  // adminData
  adminData: ''
})
 
// changes in the awning
export const mutations = {
  // state replacement
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  },
  // awning reset
  reset() {
    this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
  }
}
 
// awning actions
export const actions = {
  nuxtServerInit(store, context) {
    // who executes this code?
    console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
    // init session
    initStore(store, context)
  }
}
 
function initStore(store, context) {
  // store is the blind to be initialized
  // retrieve the session
  const session = context.app.$session()
  // has the session already been initiated?
  if (!session.value.initStoreDone) {
    // start a new blind
    console.log("nuxtServerInit, initialisation d'une nouvelle session")
    // put the blind in the session
    session.value.store = store.state
    // the blind is now initialized
    session.value.initStoreDone = true
  } else {
    console.log("nuxtServerInit, reprise d'un store existant")
    // update the store with the session store
    store.commit('replace', session.value.store)
  }
  // save the session
  session.save(context)
  // log
  console.log('initStore terminé, store=', store.state)
}

The data stored in the store is as follows:

  • line 6: [jsonSessionStarted] will be set to true as soon as a JSON session with the PHP server has been successfully initialized, whether initiated by the client or the [nuxt] server. Upon completion of this initialization, the session cookie for the PHP server will be retrieved and stored in the [phpSessionCookie] property, line 10;
  • line 8: [userAuthenticated] will be set to true as soon as authentication with the PHP server is successful, whether performed by the client or the [nuxt] server;
  • line 12: [adminData] will be the [adminData] value obtained from the PHP server once authentication is successful;
  • lines 18–22: the [replace] operation initializes the previous properties with those of an object passed as a parameter;
  • lines 24–26: the [reset] mutation restores the store properties to their initial values;
  • lines 31–37: the [nuxtServerInit] function delegates its work to the [initStore] function;
  • lines 39–60: the [initStore] function has two roles:
    • if the store has not been initialized, it is initialized and added to the session;
    • if the store has already been initialized, its value is retrieved from the [nuxt] session;
  • line 42: the nuxt session is retrieved;
  • line 44: we check if the store has been initialized:
    • if not, the initial store is placed in the session (line 48);
    • then, on line 50, we indicate that the store has been initialized;
  • lines 51–55: if the store was initialized, we use it, line 54, to initialize the store with the value contained in the session;
  • line 57: in all cases, the session is saved in the [nuxt-session] cookie, along with the store it contains;

15.10. The [plgEventBus] plugin

Image

This plugin aims to make an event bus accessible to the [nuxt] client via a [$eventBus] function injected into the [nuxt] client context. There is no need to inject it into the [nuxt] server context because the server cannot handle events. However, we have already seen that injecting it on the server side and then using it does not cause an error.


/* eslint-disable no-console */
// on crée un bus d'événements entre les vues
import Vue from 'vue'
export default (context, inject) => {
  // le bus d'événements
  const eventBus = new Vue()
  // injection d'une fonction [$eventBus] dans le contexte
  inject('eventBus', () => eventBus)
  // log
  console.log('[fonction $eventBus créée]')
}

We have already encountered this plugin in the link section. The [$eventBus] function will be available to the client via the following notation:

  • [context.app.$eventBus()] where the context is available;
  • [this.$eventBus()] in the client's [Vue.js] pages;

15.11. The components of the [Nuxt] application

Image

The [layout] component is the one from the previous examples:


<!-- view layout -->
<template>
  <!-- line -->
  <div>
    <b-row>
      <!-- three-column zone -->
      <b-col v-if="left" cols="3">
        <slot name="left" />
      </b-col>
      <!-- nine-column zone -->
      <b-col v-if="right" cols="9">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>
 
<script>
export default {
  // paramètres
  props: {
    left: {
      type: Boolean
    },
    right: {
      type: Boolean
    }
  }
}
</script>

The [navigation] component is as follows:


<template>
  <!-- bootstrap menu with three options -->
  <b-nav vertical>
    <b-nav-item to="/authentification" exact exact-active-class="active">
      Authentification
    </b-nav-item>
    <b-nav-item to="/get-admindata" exact exact-active-class="active">
      Requête AdminData
    </b-nav-item>
    <b-nav-item to="/fin-session" exact exact-active-class="active">
      Fin session impôt
    </b-nav-item>
  </b-nav>
</template>

15.12. The [nuxt] application layouts

Image

15.12.1. [default]

The [default] layout is the one used for the [nuxt-11] example in the linked paragraph:


<template>
  <div class="container">
    <b-card>
      <!-- un message -->
      <b-alert show variant="success" align="center">
        <h4>[nuxt-12] : requêtes HTTP avec axios</h4>
      </b-alert>
      <!-- la vue courante du routage -->
      <nuxt />
      <!-- message d’attente -->
      <b-alert v-if="showLoading" show variant="light">
        <strong>Requête au serveur de données en cours...</strong>
        <div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
      </b-alert>
      <!-- erreur d’une opération asynchrone -->
      <b-alert v-if="showErrorLoading" show variant="danger">
        <strong>La requête au serveur de données a échoué : {{ errorLoadingMessage }}</strong>
      </b-alert>
    </b-card>
  </div>
</template>
 
<script>
/* eslint-disable no-console */
export default {
  name: 'App',
  data() {
    return {
      showLoading: false,
      showErrorLoading: false
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[default beforeCreate]')
  },
  created() {
    console.log('[default created]')
    if (process.client) {
      // listen to the evt [loading]
      this.$eventBus().$on('loading', this.mShowLoading)
      // and event [errorLoadingMessage]
      this.$eventBus().$on('errorLoading', this.mShowErrorLoading)
    }
  },
  beforeMount() {
    console.log('[default beforeMount]')
  },
  mounted() {
    console.log('[default mounted]')
  },
  methods: {
    // message waiting management
    mShowLoading(value) {
      console.log('[default mShowLoading], showLoading=', value)
      this.showLoading = value
    },
    // asynchronous operation error
    mShowErrorLoading(value, errorLoadingMessage) {
      console.log('[default mShowErrorLoading], showErrorLoading=', value, 'errorLoadingMessage=', errorLoadingMessage)
      this.showErrorLoading = value
      this.errorLoadingMessage = errorLoadingMessage
    }
  }
}
</script>
  • lines 10–14: display the message indicating that the client [nuxt] is waiting for an asynchronous operation to complete;
  • lines 15–18: display any error message from an asynchronous operation;
  • line 37: the [created] function of the [default] page is executed before the [mounted] function of the pages;
  • line 39: if the executor is the [nuxt] client, then the [default] page listens for
    • [loading], which signals the start or end of a wait. The [mShowLoading] function is then executed;
    • [errorLoading], which signals that an error message must be displayed. The [mShowErrorLoading] function is then executed;
  • the [nuxt] pages:
    • display the loading message by emitting the [‘loading’, true] event on the event bus;
    • hide the loading message by emitting the [‘loading’, false] event on the event bus;
    • display an error message by emitting the [‘errorLoading’, true] event on the event bus;
    • hide the error message by emitting the [‘errorLoading’, false] event on the event bus;

15.12.2. [error]

The [error] layout displays a system error message (not managed by the developer):


<!-- définition HTML de la vue -->
<template>
  <!-- mise en page -->
  <Layout :left="true" :right="true">
    <!-- alerte dans la colonne de droite -->
    <template slot="right">
      <!-- message sur fond rose -->
      <b-alert show variant="danger" align="center">
        <h4>L'erreur suivante s'est produite : {{ JSON.stringify(error) }}</h4>
      </b-alert>
    </template>
    <!-- menu de navigation dans la colonne de gauche -->
    <Navigation slot="left" />
  </Layout>
</template>
 
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
 
import Layout from '@/components/layout'
import Navigation from '@/components/navigation'
 
export default {
  name: 'Error',
  // components used
  components: {
    Layout,
    Navigation
  },
  // property [props]
  props: { error: { type: Object, default: () => 'waiting ...' } },
  // life cycle
  beforeCreate() {
    // client and server
    console.log('[error beforeCreate]')
  },
  created() {
    // client and server
    console.log('[error created, error=]', this.error)
  },
  beforeMount() {
    // customer only
    console.log('[error beforeMount]')
  },
  mounted() {
    // customer only
    console.log('[error mounted]')
  }
}
</script>

15.13. The [index] page served by the [nuxt] server

Image

The [index.vue] page is unique in that it is accessible only via the [nuxt] server. No link is provided to the user to access it via the [nuxt] client. Its code is as follows:


<!-- page principale -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Initialisation de la session avec le serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'InitSession',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[index asyncData started]')
    try {
      // start a jSON session
      const dao = context.app.$dao()
      const response = await dao.initSession()
      // log
      console.log('[index asyncData response=]', response)
      // retrieve session cookie PHP for future requests
      const phpSessionCookie = dao.getPhpSessionCookie()
      // we store the PHP session cookie in the [nuxt] session
      context.store.commit('replace', { phpSessionCookie })
      // was there a mistake?
      if (response.état !== 700) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // note that the jSON session has started
      context.store.commit('replace', { jsonSessionStarted: true })
      // we return the result
      return { result: '[succès]' }
    } catch (e) {
      // log
      console.log('[index asyncData error=]', e)
      // note that session jSON has not started
      context.store.commit('replace', { jsonSessionStarted: false })
      // we report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // save the blind
      const session = context.app.$session()
      session.save(context)
      // log
      console.log('[index asyncData finished]')
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[index beforeCreate]')
  },
  created() {
    console.log('[index created]')
  },
  beforeMount() {
    console.log('[index beforeMount]')
  },
  mounted() {
    console.log('[index mounted]')
    // customer only
    if (this.showErrorLoading) {
      console.log('[index mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>
  • line 7: the page displays the result [result] of an asynchronous request (lines 46 and 51);
  • line 31: the asynchronous operation is opening a JSON session with the tax calculation server;
  • line 25: we know that when the page is requested directly from the [nuxt] server, the [asyncData] function is executed only by the server and not by the [nuxt] client, which runs once the browser has received the response from the [nuxt] server;
  • line 30: we retrieve the [dao] layer from the [nuxt] server context;
  • line 35: if the server has not yet made a request to the tax calculation server, it receives its first PHP session cookie; otherwise, it receives the last PHP session cookie it received (see the code for the [dao] layer of the [nuxt] server in the linked section);
  • line 37: this PHP session cookie is stored in the store;
  • lines 39–42: we check if the operation was successful. If not, an exception is thrown, which will be caught by the [catch] on line 47;
  • line 44: we note in the store that the JSON session with the PHP server has started;
  • line 46: the result [result] is returned and displayed on line 7;
  • lines 47–54: handle any exceptions. These can be of two types:
    • the HTTP operation on line 31 failed due to a communication error between the [nuxt] server and the PHP server;
    • the HTTP operation on line 31 succeeded but the received result reported an error (lines 39–42);
  • line 51: we note that the JSON session with the PHP server did not start;
  • line 53: the result [result] is returned and displayed on line 7. Additionally, the properties [showErrorLoading] and [errorLoadingMessage] are set, which the [nuxt] client will use to display an error message when it receives the page sent by the [nuxt] server (lines 72–79);
  • lines 54–60: code executed in all cases (success or failure);
  • line 56: we retrieve the [nuxt] session from the [nuxt] server context;
  • line 57: we save it;
  • lines 63–68: once the [asyncData] function has finished, the [nuxt] server executes the [beforeCreate] and [create] functions;

Note: the [nuxt] server’s execution of the [index] page may fail, for example, if the tax calculation server is not running when the [nuxt] application is launched:

Image

In this case, the only solution is to start the tax calculation server and then the [nuxt] application itself, since the navigation menu does not offer an option to initiate a JSON session with the tax calculation server;

15.14. The [index] page executed by the [nuxt] client

The [index] page is only executed by the [nuxt] client after the [nuxt] server has sent it to the client. The server has sent the [result] information and, if applicable, [showErrorLoading] and [errorLoadingMessage].

We know that the [asyncData] function will not be executed. This leaves the lifecycle functions, particularly the [mounted] function:


mounted() {
    console.log('[index mounted]')
    // client seulement
    if (this.showErrorLoading) {
      console.log('[index mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
}
  • The [nuxt] client automatically includes the [result] element and, if applicable, the [showErrorLoading] and [errorLoadingMessage] elements sent to it by the [nuxt] server in the page properties:
  • the [result] property is displayed by line 7;
  • The properties [showErrorLoading, errorLoadingMessage] are used by the [mounted] method: on line 4, the [showErrorLoading] property is checked. If it is true, on line 6, the client’s [nuxt] event bus is used to signal that there is an error message to display;
  • the [errorLoading] event triggered on line 6 is intercepted by the [layouts/default] page described in the linked section;

15.15. The [authentication] page executed by the [nuxt] server

The [authentication] page is responsible for authenticating a user with the tax calculation server. Its code is as follows:


<!-- page d’authentification -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Authentification auprès du serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'Authentification',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[authentification asyncData started]')
    if (process.client) {
      // start waiting for customer [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // authenticate to the server
      const dao = context.app.$dao()
      const response = await dao.authentifierUtilisateur('admin', 'admin')
      // log
      console.log('[authentification asyncData response=]', response)
      // result
      const userAuthenticated = response.état === 200
      // we note whether the user is authenticated or not
      context.store.commit('replace', { userAuthenticated })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // authentication error?
      if (!userAuthenticated) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // we return the result
      return { result: '[succès]' }
    } catch (e) {
      // we report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[authentification asyncData finished]')
      if (process.client) {
        // end customer waiting [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[authentification beforeCreate]')
  },
  created() {
    console.log('[authentification created]')
  },
  beforeMount() {
    console.log('[authentification beforeMount]')
  },
  mounted() {
    console.log('[authentification mounted]')
    // customer only
    if (this.showErrorLoading) {
      console.log('[authentification mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>
  • line 7: the page displays the result [result] of the asynchronous request [asyncData] from lines 25–65;
  • lines 28–33: the server does not execute these lines intended for the client [nuxt];
  • line 36: the [dao] layer is retrieved from the [nuxt] server;
  • line 37: we authenticate with the tax calculation server using the test credentials [admin, admin], which are the only ones accepted by the tax calculation server;
  • line 41: the authentication operation is successful only if the response status is 200;
  • line 43: we set the [userAuthenticated] property in the store;
  • lines 44–46: the store is saved in the [nuxt] session;
  • lines 48–51: if authentication failed, throw an exception with the error message sent by the tax calculation server;
  • otherwise, on line 53, return a success result that will be displayed on line 7;
  • lines 54–57: in case of an error, three page properties are set [result, showErrorLoading, errorLoadingMessage]. The [result] property will be displayed on line 7. The three properties will be sent to the client [nuxt];
  • lines 60–63: are not executed by the [nuxt] server;
  • once [asyncData] has returned its result, it is displayed on line 7. Then the [beforeCreate] (lines 67–69) and [created] (lines 70–72) methods are executed;
  • that’s it;

Note: The [authentification] page may fail to execute on the [nuxt] server, for example, if the JSON session with the tax calculation server has not been initialized. This can be done as follows:

  • delete the PHP session cookie from your browser (to start from scratch):

Image

  • launch the [nuxt] application while the calculation server has not been started: you will get an error;
  • launch the tax calculation server;
  • enter the URL [/authentication] directly into the browser’s address bar:

Image

In this case, the only solution is to reload the [index] page again.

15.16. The [authentication] page executed by the [nuxt] client

Let’s look at the page’s code again:


<!-- page d’authentification -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Authentification auprès du serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'Authentification',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[authentification asyncData started]')
    if (process.client) {
      // start waiting for customer [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // authenticate to the server
      const dao = context.app.$dao()
      const response = await dao.authentifierUtilisateur('admin', 'admin')
      // log
      console.log('[authentification asyncData response=]', response)
      // result
      const userAuthenticated = response.état === 200
      // we note whether the user is authenticated or not
      context.store.commit('replace', { userAuthenticated })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // authentication error?
      if (!userAuthenticated) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // we return the result
      return { result: '[succès]' }
    } catch (e) {
      // we report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[authentification asyncData finished]')
      if (process.client) {
        // end customer waiting [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[authentification beforeCreate]')
  },
  created() {
    console.log('[authentification created]')
  },
  beforeMount() {
    console.log('[authentification beforeMount]')
  },
  mounted() {
    console.log('[authentification mounted]')
    // customer only
    if (this.showErrorLoading) {
      console.log('[authentification mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

There are two scenarios in which the [authentication] page is executed by the [nuxt] client:

  1. the [nuxt] client runs after the [nuxt] server has sent the [authentication] page to the [nuxt] client’s browser;
  2. the [nuxt] client because the user clicked the [Authentication] link in the navigation menu:

Image

Let’s first examine the first scenario. In this case, the [nuxt] client does not execute the [asyncData] function. It incorporates the [result] element and, if applicable, the [showErrorLoading] and [errorLoadingMessage] elements sent to it by the [nuxt] server into the page’s properties:

  • the [result] property is displayed by line 7;
  • the [showErrorLoading, errorLoadingMessage] properties are used by the [mounted] method: on line 79, the [showErrorLoading] property is checked. If it is true, on line 81, the [nuxt] client’s event bus is used to signal that there is an error message to display;

The mechanism for displaying the error message was explained for the [index] page in the link section.

Case 2 is when the [nuxt] client runs after the user clicks the [Authentication] link. In this case, the [nuxt] client runs independently and not after the [nuxt] server. The [asyncData] function is then executed. We provide only the details that differ from the explanations given for the page executed by the [nuxt] server:

  • lines 28–33: the [nuxt] client requests that the loading message be displayed and that any previously displayed error message be cleared;
  • line 36: it is now the [dao] layer of the [nuxt] client that is retrieved here;
  • lines 60–63: the [nuxt] client requests that the loading message be removed;
  • Once [asyncData] has finished, the page lifecycle will proceed. The [mounted] function in lines 76–83 will be executed. If an error occurred, the error message will then be displayed;

Note: To trigger an error, follow the procedure explained for the [nuxt] server at the end of the link section, but instead of requesting the [authentication] page by typing its URL into the address bar, use the [Authentication] link in the navigation menu. This will cause the [nuxt] client to run.

15.17. The [get-admindata] page

The code for the [get-admindata] page is as follows:


<!-- vue get-admindata -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <b-alert slot="right" show variant="secondary"> Demande de [adminData] au serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'GetAdmindata',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[get-admindata asyncData started]')
    if (process.client) {
      // start waiting
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // the data [admindata] is requested
      const response = await context.app.$dao().getAdminData()
      // log
      console.log('[get-admindata asyncData response=]', response)
      // result
      const adminData = response.état === 1000 ? response.réponse : ''
      // put the data in the store
      context.store.commit('replace', { adminData })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // was there a mistake?
      if (!adminData) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // return the value received
      return { result: adminData }
    } catch (e) {
      // we report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[get-admindata asyncData finished]')
      if (process.client) {
        // end waiting
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[get-admindata beforeCreate]')
  },
  created() {
    console.log('[get-admindata created]')
  },
  beforeMount() {
    console.log('[get-admindata beforeMount]')
  },
  mounted() {
    console.log('[get-admindata mounted]')
    // customer
    if (this.showErrorLoading) {
      console.log('[get-admindata mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

This page is very similar to the [authentication] page. The explanations are the same whether it is executed by the [nuxt] server or by the [nuxt] client. Note, however, that line 7 does not display success/failure as before, but rather the value of the data received from the tax calculation server (line 52):

Image

The result above is obtained using both the server and the [nuxt] client. To trigger an error, request the [get-admindata] page—either via the server or the [nuxt] client—without being authenticated:

Image

15.18. The [fin-session] page

The page code is as follows:


<!-- page principale -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Fin de la session avec le serveur de calcul de l'impôt : {{ result }} </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-console */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'FinSession',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[fin-session asyncData started]')
    // customer case [nuxt]
    if (process.client) {
      // start waiting
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // a new session PHP is requested from the tax calculation server
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // log
      console.log('[fin-session asyncData response=]', response)
      // was there a mistake?
      if (response.état !== 400) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // the server has sent a new session cookie PHP
      // we retrieve it for both the server and the nuxt client
      // if this code is executed by the [nuxt] client, the PHP session cookie must be set in the nuxt session
      // so that the [plgDao] plugin on the [nuxt] server can retrieve it and initialize the [dao] layer with
      // if this code is executed by the [nuxt] server, the PHP session cookie must be set in the nuxt session
      // so that the client routing [nuxt] can retrieve it and pass it to the browser
      const phpSessionCookie = dao.getPhpSessionCookie()
      // we note in the store that the session jSON has been started and we store the session cookie PHP
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // we return the result
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    } catch (e) {
      // log
      console.log('[fin-session asyncData error=]', e)
      // report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[fin-session asyncData finished]')
      if (process.client) {
        // end waiting
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // life cycle
  beforeCreate() {
    console.log('[fin-session beforeCreate]')
  },
  created() {
    console.log('[fin-session created]')
  },
  beforeMount() {
    console.log('[fin-session beforeMount]')
  },
  mounted() {
    console.log('[fin-session mounted]')
    // customer only
    if (this.showErrorLoading) {
      console.log('[fin-session mounted, showErrorLoading=true]')
      this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
    }
  }
}
</script>

The code is very similar to that on the previous pages, and the explanations are the same. There is just one point to note: the asynchronous operation on line 38 causes the tax calculation server to send a new PHP session cookie. The instructions for handling this cookie differ depending on whether the server or the [nuxt] client is executing this code.

Let’s start with the [nuxt] server:

  • line 37: this is the [dao] layer of the [nuxt] server that is instantiated. Let’s recall the code for its constructor:

// constructeur
  constructor(axios, phpSessionCookie) {
    // bibliothèque axios
    this.axios = axios
    // valeur du cookie de session
    this.phpSessionCookie = phpSessionCookie
    // nom du cookie de session du serveur PHP
    this.phpSessionCookieName = 'PHPSESSID'
}

In line 1, we see that the constructor needs the current PHP session cookie—the last one received, whether from the server or the [nuxt] client;

  • line 52: the [nuxt] server retrieves the cookie for the new PHP session or the old cookie if the session termination operation failed;
  • line 54: the PHP session cookie is placed in the store and then saved in the [nuxt] session on lines 56–57;
  • after the server, it is the client [nuxt] that executes the [end-session] page with the data sent by the server. We know that it will not execute the [asyncData] function;
  • Ultimately, after the server and the [nuxt] client have finished their work, we know that the PHP cookie required for communication with the tax calculation server is in the [nuxt] session;

The fact that the PHP cookie is in the [nuxt] session is sufficient for the server, because that is where its [dao] layer will retrieve it. In the [server/plgDao] plugin that initializes the server’s [dao] layer, we have written:


/* eslint-disable no-console */
// on crée un point d'accès à la couche [Dao]
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // configuration axios
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // on récupère le cookie de session
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // instanciation de la couche [dao]
  const dao = new Dao(context.$axios, phpSessionCookie)
  // injection d'une fonction [$dao] dans le contexte
  inject('dao', () => dao)
  // log
  console.log('[fonction server $dao créée]')
}
  • Line 13: the [dao] layer of the [nuxt] server is instantiated with the PHP session cookie taken from the [nuxt] session, lines 9–10;

For the [nuxt] client, it’s a different story. It is not the client that sends the cookie, but the browser that executes it. However, this browser does not know the cookie for the new PHP session received by the [nuxt] server. If we use the links in the navigation menu [3]:

Image

The tax calculation server will receive an outdated PHP session cookie from the browser and will respond that no JSON session is associated with this cookie. We need to find a way to pass the new PHP session cookie to the browser.

We can use routing middleware to do this:

Image

The [client/routing] script is the routing middleware declared in the [nuxt.config] file:


// router
  router: {
    // application URL root
    base: '/nuxt-12/',
    // routing middleware
    middleware: ['routing']
},

The [middleware/routing] script is as follows:


/* eslint-disable no-console */
 
// on importe le middleware du client
import clientRouting from './client/routing'
 
export default function(context) {
  // qui exécute ce code ?
  console.log('[middleware], process.server', process.server, ', process.client=', process.client)
  if (process.client) {
    // routage client
    clientRouting(context)
  }
}
  • Lines 9–12: We only route the client using a function imported on line 4;

The script [middleware/client/routing] is as follows:


/* eslint-disable no-console */
export default function(context) {
  // who executes this code?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // management of the PHP session cookie in the browser
  // the browser's PHP session cookie must be identical to the one found in the nuxt session
  // acion [fin-session] receives a new cookie PHP (server as nuxt client)
  // if the server receives it, the client must pass it on to the browser
  // for its own exchanges with the PHP server
  // this is customer routing
 
  // retrieve the session cookie PHP
  const phpSessionCookie = context.store.state.phpSessionCookie
  if (phpSessionCookie) {
    // if it exists, we assign the PHP session cookie to the browser
    document.cookie = phpSessionCookie
  }
}

Let’s return to the situation immediately after the [fin-session] page is executed by the [nuxt] server:

Image

If you click on one of the links in the [3] menu, the [nuxt] client takes over. Since there will be a page change, the client’s routing script will execute:

  • line 13: the PHP session cookie is found in the [nuxt] application store;
  • line 14: if it is not empty, it is sent to the browser (line 16). From this point on, the [nuxt] client’s browser has the correct PHP session cookie;

The [client/routing] script runs every time the [nuxt] client changes pages. The script’s code works regardless of the target page: essentially, most of the time, it sends the browser a PHP session cookie that it already has, except in two cases:

  • immediately after the application starts, the [nuxt] server executes the [index] page and receives a first PHP session cookie that the [nuxt] client’s browser does not have;
  • when the [nuxt] server executes the [end-session] page as just explained;

Now let’s examine the case where the [end-session] page is executed by the [nuxt] client only, because the user clicked on its link in the navigation menu. It is now the [nuxt] client that executes the [asyncData] function:


try {
      // a new session PHP is requested from the tax calculation server
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // log
      console.log('[fin-session asyncData response=]', response)
      // was there a mistake?
      if (response.état !== 400) {
        // the error is in response.réponse
        throw new Error(response.réponse)
      }
      // the server has sent a new session cookie PHP
      // we retrieve it for both the server and the nuxt client
      // if this code is executed by the [nuxt] client, the PHP session cookie must be set in the nuxt session
      // so that the [plgDao] plugin on the [nuxt] server can retrieve it and initialize the [dao] layer with
      // if this code is executed by the [nuxt] server, the PHP session cookie must be set in the nuxt session
      // so that the client routing [nuxt] can retrieve it and pass it to the browser
      const phpSessionCookie = dao.getPhpSessionCookie()
      // we note in the store that the session jSON has been started and we store the session cookie PHP
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // save the store in the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // we return the result
      return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
    } catch (e) {
      // log
      console.log('[fin-session asyncData error=]', e)
      // report the error
      return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[fin-session asyncData finished]')
      if (process.client) {
        // end waiting
        context.app.$eventBus().$emit('loading', false)
      }
    }
  • Line 3: This is the [dao] layer of the [nuxt] client that is retrieved here;
  • Line 18: The PHP session cookie retrieved by the client [nuxt]'s [dao] layer is stored, placed in the store (line 20), and then saved in the [nuxt] session (lines 22–23);
  • From there on, everything works fine because we know that the [dao] layer of the [nuxt] server will retrieve the PHP session cookie from the [nuxt] session;

15.19. Execution

To run this example, you must first delete the [nuxt] session cookie and the PHP cookie from the browser running the [nuxt] client to start with a clean slate. Below is an example using the Chrome browser:

Image

15.20. Conclusion

This example was particularly complex. It brought together knowledge acquired in previous examples: store persistence in a [nuxt] session, function injection plugins, routing middleware, and error handling for asynchronous operations. The complexity was heightened by the fact that we wanted the user to be able to use both the navigation menu links and manually type in URLs without breaking the application. To achieve this, we had to examine how each page behaved depending on whether it was executed by the client or the server [nuxt].

This consistency in client and [Nuxt] server behavior is not essential. Consider the common scenario where:

  • the first page is served by the [nuxt] server;
  • all subsequent pages are served by the [nuxt] client, which then operates in [SPA] mode;

Nevertheless, even in this case, you must verify how all pages render when executed by the [nuxt] server, as this is what search engines will see when they request them.