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',
  /*
   ** Page headers
   */
  head: {
    title: 'Introduction to [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 the Webpack configuration here
     */
    extend(config, ctx) { }
  },
  // source code directory
  srcDir: 'nuxt-12',
  // router
  router: {
    // application URL root
    base: '/nuxt-12/',
    // routing middleware
    middleware: ['routing']
  },
  // server
  server: {
    // server port, 3000 by default
    port: 81,
    // network addresses listened to, default is localhost: 127.0.0.1
    // 0.0.0.0 = all network addresses on the machine
    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 {

  // constructor
  constructor(axios) {
    this.axios = axios;
    // session cookie
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
  }

  // initialize session
  async initSession() {
    // HTTP request options [get /main.php?action=init-session&type=json]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: 'init-session',
        type: 'json'
      }
    };
    // Execute the HTTP request
    return await this.getRemoteData(options);
  }

  async authenticateUser(user, password) {
    // HTTP request options [post /main.php?action=authenticate-user]
    const options = {
      method: "POST",
      headers: {
        'Content-type': 'application/x-www-form-urlencoded',
      },
      // POST body
      data: qs.stringify({
        user: user,
        password: password
      }),
      // URL parameters
      params: {
        action: 'authenticate-user'
      }
    };
    // execute the HTTP request
    return await this.getRemoteData(options);
  }

  async getAdminData() {
    // HTTP request options [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: 'get-admindata'
      }
    };
    // Execute the HTTP request
    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 the HTTP request
    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's response is in [error.response]
        response = error.response;
      } else {
        // re-raise the error
        throw error;
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + the response itself)
    // retrieve the session cookie if it exists
    const setCookie = response.headers['set-cookie'];
    if (setCookie) {
      // setCookie is an array
      // we search for the session cookie in this array
      let found = false;
      let i = 0;
      while (!found && i < setCookie.length) {
        // search for the session cookie
        const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // store the session cookie
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // found
          found = true;
        } else {
          // next element
          i++;
        }
      }
    }
    // The server's response is in [response.data]
    return response.data;
  }
}

// export the class
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:


// constructor
  constructor(axios) {
    this.axios = axios;
    // session cookie
    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:


// constructor
  constructor(axios) {
    this.axios = axios;
    // session cookie
    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:


// constructor
  constructor(axios, phpSessionCookie) {
    // axios library
    this.axios = axios
    // session cookie value
    this.phpSessionCookie = phpSessionCookie
    // name of the PHP server's session cookie
    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 the tax calculation session
  async finSession() {
    // HTTP request options [get /main.php?action=fin-session]
    const options = {
      method: 'GET',
      // URL parameters
      params: {
        action: 'end-session'
      }
    }
    // execute the HTTP request
    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 the HTTP request
    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's response is in [error.response]
        response = error.response;
      } else {
        // we re-throw the error
        throw error;
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + the response itself)
    // retrieve the session cookie if it exists
    const setCookie = response.headers['set-cookie'];
    if (setCookie) {
      // setCookie is an array
      // we search for the session cookie in this array
      let found = false;
      let i = 0;
      while (!found && i < setCookie.length) {
        // search for the session cookie
        const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // store the session cookie
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // found
          found = true;
        } else {
          // next element
          i++;
        }
      }
    }
    // The server's 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 any headers?
      if (!options.headers) {
        // create an empty object
        options.headers = {}
      }
      // PHP session cookie header
      options.headers.Cookie = this.phpSessionCookie
    }
    // execute the HTTP request
    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's response is in [error.response]
        response = error.response
      } else {
        // we throw the error
        throw error
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + the response itself)
    // search for the PHP session cookie in the received cookies
    // all received cookies
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // cookies is an array
      // search for the PHP session cookie in this array
      let found = false
      let i = 0
      while (!found && i < cookies.length) {
        // we search for the PHP session cookie
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // store the PHP session cookie
          const phpSessionCookie = results[1]
          // Does it contain the word [deleted]?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // we have the correct PHP session cookie
            this.phpSessionCookie = phpSessionCookie
            // Found
            found = true
          } else {
            // next element
            i++
          }
        } else {
          // next element
          i++
        }
      }
    }
    // The server's 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:


// access the PHP session cookie
  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 {
  // constructor
  constructor(axios) {
    this.axios = axios;
  }

  // initialize session
  async initSession() {
    // HTTP request options [get /main.php?action=init-session&type=json]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: "init-session",
        type: "json"
      }
    };
    // Execute the HTTP request
    return await this.getRemoteData(options);
  }

  async authenticateUser(user, password) {
    // HTTP request options [post /main.php?action=authenticate-user]
    const options = {
      method: "POST",
      headers: {
        "Content-type": "application/x-www-form-urlencoded"
      },
      // POST body
      data: qs.stringify({
        username: user,
        password: password
      }),
      // URL parameters
      params: {
        action: "authenticate-user"
      }
    };
    // execute the HTTP request
    return await this.getRemoteData(options);
  }

  async getAdminData() {
    // HTTP request options  [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: "get-admindata"
      }
    };
    // execute the HTTP request
    const data = await this.getRemoteData(options);
    // result
    return data;
  }

  async getRemoteData(options) {
    // Execute the HTTP request
    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's response is in [error.response]
        response = error.response;
      } else {
        // we re-throw the error
        throw error;
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + the response itself)
    // The server's response is in [response.data]
    return response.data;
  }
}

// export the class
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() {
    // HTTP request options  [get /main.php?action=end-session]
    const options = {
      method: 'GET',
      // URL parameters
      params: {
        action: 'end-session'
      }
    }
    // execute the HTTP request
    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 the HTTP request
    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's response is in [error.response]
        response = error.response
      } else {
        // we throw the error
        throw error
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + the response itself)
    // search for the PHP session cookie in the received cookies
    // all received cookies
    const cookies = response.headers['set-cookie']
    if (cookies) {
      // cookies is an array
      // search for the PHP session cookie in this array
      let found = false
      let i = 0
      while (!found && i < cookies.length) {
        // we search for the PHP session cookie
        const results = RegExp('^(' + this.phpSessionCookieName + '.+?)$').exec(cookies[i])
        if (results) {
          // store the PHP session cookie
          const phpSessionCookie = results[1]
          // Does it contain the word [deleted]?
          const results2 = RegExp(this.phpSessionCookieName + '=deleted').exec(phpSessionCookie)
          if (!results2) {
            // we have the correct PHP session cookie
            this.phpSessionCookie = phpSessionCookie
            // Found
            found = true
          } else {
            // next element
            i++
          }
        } else {
          // next element
          i++
        }
      }
    }
    // The server's 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:


// access the PHP session cookie
  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 */
// session definition
const session = {
  // session content
  value: {
    // store not initialized
    initStoreDone: false,
    // Vuex store value
    store: ''
  },
  // Save the session to a cookie
  save(context) {
    // Save the store to the session
    this.value.store = context.store.state
    console.log('nuxt-session save=', this.value)
    // Save the session value
    context.app.$cookies.set('nuxt-session', this.value, { path: context.base, maxAge: context.env.maxAge })
  },
  // reset the session
  reset(context) {
    console.log('nuxt-session reset')
    // reset the store
    context.store.commit('reset')
    // Save the new store to the session and save the session
    this.save(context)
  }
}
// export the 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 the session
import session from '@/entities/session'

export default (context, inject) => {
  // server session management
  console.log('[plugin server plgSession]')

  // Is there an existing session?
  const value = context.app.$cookies.get('nuxt-session')
  if (!value) {
    // new session
    console.log("[plugin server plgSession], starting a new session")
  } else {
    // existing session
    console.log("[plugin server plgSession], resuming an existing session")
    session.value = value
  }

  // inject a function into [context, Vue] that will return the current session
  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 the session
import session from '@/entities/session'

export default (context, inject) => {
  // client session management
  console.log('[client plugin plgSession], resuming the [nuxt] session from the server')
  // retrieve the existing session from the Nuxt server
  session.value = context.app.$cookies.get('nuxt-session')

  // inject a function into [context, Vue] that will make the session current
  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 */
// We create an access point to the [Dao] layer
import Dao from '@/api/client/Dao'
export default (context, inject) => {
  // Axios configuration
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  context.$axios.defaults.withCredentials = context.env.withCredentials
  // instantiate the [dao] layer
  const dao = new Dao(context.$axios)
  // injecting a function [$dao] into the context
  inject('dao', () => dao)
  // log
  console.log('[client function $dao created]')
}
  • 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:

// 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 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 */
// we create an entry point to the [Dao] layer
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // Axios configuration
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // retrieve the session cookie
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // instantiate the [dao] layer
  const dao = new Dao(context.$axios, phpSessionCookie)
  // inject a function [$dao] into the context
  inject('dao', () => dao)
  // log
  console.log('[server function $dao created]')
}
  • 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:

// 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 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 */

// store state
export const state = () => ({
  // JSON session started
  jsonSessionStarted: false,
  // user authenticated
  userAuthenticated: false,
  // PHP session cookie
  phpSessionCookie: '',
  // adminData
  adminData: ''
})

// store mutations
export const mutations = {
  // replace state
  replace(state, newState) {
    for (const attr in newState) {
      state[attr] = newState[attr]
    }
  },
  // reset the store
  reset() {
    this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
  }
}

// store actions
export const actions = {
  nuxtServerInit(store, context) {
    // Who is running this code?
    console.log('nuxtServerInit, client=', process.client, 'server=', process.server, 'env=', context.env)
    // initialize session
    initStore(store, context)
  }
}

function initStore(store, context) {
  // store is the store to be initialized
  // retrieve the session
  const session = context.app.$session()
  // Has the session already been initialized?
  if (!session.value.initStoreDone) {
    // start a new store
    console.log("nuxtServerInit, initializing a new session")
    // Add the store to the session
    session.value.store = store.state
    // The store is now initialized
    session.value.initStoreDone = true
  } else {
    console.log("nuxtServerInit, resuming an existing store")
    // update the store with the session store
    store.commit('replace', session.value.store)
  }
  // save the session
  session.save(context)
  // log
  console.log('initStore completed, 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 */
// create an event bus between views
import Vue from 'vue'
export default (context, inject) => {
  // the event bus
  const eventBus = new Vue()
  // inject a function [$eventBus] into the context
  inject('eventBus', () => eventBus)
  // log
  console.log('[$eventBus function created]')
}

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 area -->
      <b-col v-if="left" cols="3">
        <slot name="left" />
      </b-col>
      <!-- nine-column section -->
      <b-col v-if="right" cols="9">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

<script>
export default {
  // settings
  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="/authentication" exact exact-active-class="active">
      Authentication
    </b-nav-item>
    <b-nav-item to="/get-admindata" exact exact-active-class="active">
      AdminData Request
    </b-nav-item>
    <b-nav-item to="/end-session" exact exact-active-class="active">
      End Tax Session
    </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>
      <!-- a message -->
      <b-alert show variant="success" align="center">
        <h4>[nuxt-12]: HTTP requests with axios</h4>
      </b-alert>
      <!-- the current routing view -->
      <nuxt />
      <!-- loading message -->
      <b-alert v-if="showLoading" show variant="light">
        <strong>Request to the data server in progress...</strong>
        <div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
      </b-alert>
      <!-- error in an asynchronous operation -->
      <b-alert v-if="showErrorLoading" show variant="danger">
        <strong>The request to the data server failed: {{ errorLoadingMessage }}</strong>
      </b-alert>
    </b-card>
  </div>
</template>

<script>
/* eslint-disable no-console */
export default {
  name: 'App',
  data() {
    return {
      showLoading: false,
      showErrorLoading: false
    }
  },
  // lifecycle
  beforeCreate() {
    console.log('[default beforeCreate]')
  },
  created() {
    console.log('[default created]')
    if (process.client) {
      // listen for the [loading] event
      this.$eventBus().$on('loading', this.mShowLoading)
      // as well as the [errorLoadingMessage] event
      this.$eventBus().$on('errorLoading', this.mShowErrorLoading)
    }
  },
  beforeMount() {
    console.log('[default beforeMount]')
  },
  mounted() {
    console.log('[default mounted]')
  },
  methods: {
    // Handle the loading message
    mShowLoading(value) {
      console.log('[default mShowLoading], showLoading=', value)
      this.showLoading = value
    },
    // error in an asynchronous operation
    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):


<!-- HTML definition of the view -->
<template>
  <!-- layout -->
  <Layout :left="true" :right="true">
    <!-- alert in the right column -->
    <template slot="right">
      <!-- message on a pink background -->
      <b-alert show variant="danger" align="center">
        <h4>The following error occurred: {{ JSON.stringify(error) }}</h4>
      </b-alert>
    </template>
    <!-- navigation menu in the left column -->
    <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
  },
  // [props] property
  props: { error: { type: Object, default: () => 'waiting ...' } },
  // lifecycle
  beforeCreate() {
    // client and server
    console.log('[error beforeCreate]')
  },
  created() {
    // client and server
    console.log('[error created, error=]', this.error)
  },
  beforeMount() {
    // client only
    console.log('[error beforeMount]')
  },
  mounted() {
    // client 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:


<!-- main page -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Initializing the session with the tax calculation server: {{ 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 the PHP session cookie for future requests
      const phpSessionCookie = dao.getPhpSessionCookie()
      // store the PHP session cookie in the [nuxt] session
      context.store.commit('replace', { phpSessionCookie })
      // Was there an error?
      if (response.status !== 700) {
        // The error is in response.response
        throw new Error(response.response)
      }
      // Note that the JSON session has started
      context.store.commit('replace', { jsonSessionStarted: true })
      // return the result
      return { result: '[success]' }
    } catch (e) {
      // log
      console.log('[index asyncData error=]', e)
      // note that the JSON session has not started
      context.store.commit('replace', { jsonSessionStarted: false })
      // report the error
      return { result: '[failure]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // save the store
      const session = context.app.$session()
      session.save(context)
      // log
      console.log('[index asyncData finished]')
    }
  },
  // lifecycle
  beforeCreate() {
    console.log('[index beforeCreate]')
  },
  created() {
    console.log('[index created]')
  },
  beforeMount() {
    console.log('[index beforeMount]')
  },
  mounted() {
    console.log('[index mounted]')
    // client 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 only
    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:


<!-- authentication page -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <b-alert slot="right" show variant="warning">Authentication with the tax calculation server: {{ result }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Authentication',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[asyncData authentication started]')
    if (process.client) {
      // Start waiting for the client [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // Authenticate with the server
      const dao = context.app.$dao()
      const response = await dao.authenticateUser('admin', 'admin')
      // log
      console.log('[asyncData authentication response=]', response)
      // result
      const userAuthenticated = response.status === 200
      // note whether the user is authenticated or not
      context.store.commit('replace', { userAuthenticated })
      // save the store to the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // Authentication error?
      if (!userAuthenticated) {
        // The error is in response.response
        throw new Error(response.response)
      }
      // return the result
      return { result: '[success]' }
    } catch (e) {
      // report the error
      return { result: '[failure]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[asyncData authentication finished]')
      if (process.client) {
        // end waiting for client [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // lifecycle
  beforeCreate() {
    console.log('[authentication beforeCreate]')
  },
  created() {
    console.log('[authentication created]')
  },
  beforeMount() {
    console.log('[authentication beforeMount]')
  },
  mounted() {
    console.log('[authentication mounted]')
    // client only
    if (this.showErrorLoading) {
      console.log('[authentication 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:


<!-- authentication page -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <b-alert slot="right" show variant="warning">Authentication with the tax calculation server: {{ result }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Authentication',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[asyncData authentication started]')
    if (process.client) {
      // Start waiting for the client [nuxt]
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // authenticate with the server
      const dao = context.app.$dao()
      const response = await dao.authenticateUser('admin', 'admin')
      // log
      console.log('[asyncData authentication response=]', response)
      // result
      const userAuthenticated = response.status === 200
      // note whether the user is authenticated or not
      context.store.commit('replace', { userAuthenticated })
      // save the store to the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // Authentication error?
      if (!userAuthenticated) {
        // The error is in response.response
        throw new Error(response.response)
      }
      // return the result
      return { result: '[success]' }
    } catch (e) {
      // report the error
      return { result: '[failure]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[asyncData authentication finished]')
      if (process.client) {
        // end of client wait [nuxt]
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // lifecycle
  beforeCreate() {
    console.log('[authentication beforeCreate]')
  },
  created() {
    console.log('[authentication created]')
  },
  beforeMount() {
    console.log('[authentication beforeMount]')
  },
  mounted() {
    console.log('[authentication mounted]')
    // client only
    if (this.showErrorLoading) {
      console.log('[authentication 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:


<!-- get-admindata view -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message -->
    <b-alert slot="right" show variant="secondary"> Request for [adminData] from the tax calculation server: {{ 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 {
      // request the [admindata] data
      const response = await context.app.$dao().getAdminData()
      // log
      console.log('[get-admindata asyncData response=]', response)
      // result
      const adminData = response.status === 1000 ? response.response : ''
      // We put the data into the store
      context.store.commit('replace', { adminData })
      // save the store to the [nuxt] session
      const session = context.app.$session()
      session.save(context)
      // Was there an error?
      if (!adminData) {
        // The error is in response.response
        throw new Error(response.response)
      }
      // return the received value
      return { result: adminData }
    } catch (e) {
      // report the error
      return { result: '[failure]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[get-admindata asyncData finished]')
      if (process.client) {
        // end waiting
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // lifecycle
  beforeCreate() {
    console.log('[get-admindata beforeCreate]')
  },
  created() {
    console.log('[get-admindata created]')
  },
  beforeMount() {
    console.log('[get-admindata beforeMount]')
  },
  mounted() {
    console.log('[get-admindata mounted]')
    // client
    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:


<!-- main page -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">Session with the tax calculation server has ended: {{ result }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'EndSession',
  // components used
  components: {
    Layout,
    Navigation
  },
  // asynchronous data
  async asyncData(context) {
    // log
    console.log('[asyncData session started]')
    // client case [nuxt]
    if (process.client) {
      // start waiting
      context.app.$eventBus().$emit('loading', true)
      // no error
      context.app.$eventBus().$emit('errorLoading', false)
    }
    try {
      // request a new PHP session from the tax calculation server
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // log
      console.log('[end-session asyncData response=]', response)
      // Was there an error?
      if (response.status !== 400) {
        // the error is in response.response
        throw new Error(response.response)
      }
      // The server sent a new PHP session cookie
      // 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 placed 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 placed in the Nuxt session
      // so that the [nuxt] client's routing can retrieve it and pass it to the browser
      const phpSessionCookie = dao.getPhpSessionCookie()
      // we note in the store that the JSON session has started and store the PHP session cookie
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // save the store to the [Nuxt] session
      const session = context.app.$session()
      session.save(context)
      // Return the result
      return { result: "[success]. The JSON session remains initialized, but you are no longer authenticated." }
    } catch (e) {
      // log
      console.log('[end-session asyncData error=]', e)
      // report the error
      return { result: '[failure]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[end-session asyncData finished]')
      if (process.client) {
        // end waiting
        context.app.$eventBus().$emit('loading', false)
      }
    }
  },
  // lifecycle
  beforeCreate() {
    console.log('[end-session beforeCreate]')
  },
  created() {
    console.log('[end-session created]')
  },
  beforeMount() {
    console.log('[end-session beforeMount]')
  },
  mounted() {
    console.log('[end-session mounted]')
    // client only
    if (this.showErrorLoading) {
      console.log('[end-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:

// constructor
  constructor(axios, phpSessionCookie) {
    // axios library
    this.axios = axios
    // session cookie value
    this.phpSessionCookie = phpSessionCookie
    // name of the PHP server's session cookie
    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 */
// we create an access point to the [Dao] layer
import Dao from '@/api/server/Dao'
export default (context, inject) => {
  // Axios configuration
  context.$axios.defaults.timeout = context.env.timeout
  context.$axios.defaults.baseURL = context.env.baseURL
  // retrieve the session cookie
  const store = context.app.$session().value.store
  const phpSessionCookie = store ? store.phpSessionCookie : ''
  console.log('session=', context.app.$session().value, 'phpSessionCookie=', phpSessionCookie)
  // instantiate the [dao] layer
  const dao = new Dao(context.$axios, phpSessionCookie)
  // inject a function [$dao] into the context
  inject('dao', () => dao)
  // log
  console.log('[server function $dao created]')
}
  • 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 */

// import the client middleware
import clientRouting from './client/routing'

export default function(context) {
  // Who is executing this code?
  console.log('[middleware], process.server', process.server, ', process.client=', process.client)
  if (process.client) {
    // client routing
    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 is executing this code?
  console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
  // managing the PHP session cookie in the browser
  // The browser's PHP session cookie must match the one found in the Nuxt session
  // The [end-session] action receives a new PHP cookie (both server and Nuxt client)
  // if the server receives it, the client must pass it to the browser
  // for its own communication with the PHP server
  // we are here in client-side routing

  // We retrieve the PHP session cookie
  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 {
      // we request a new PHP session from the tax calculation server
      const dao = context.app.$dao()
      const response = await dao.finSession()
      // log
      console.log('[end-session asyncData response=]', response)
      // Was there an error?
      if (response.status !== 400) {
        // the error is in response.response
        throw new Error(response.response)
      }
      // The server sent a new PHP session cookie
      // 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 placed 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 placed in the Nuxt session
      // so that the [nuxt] client's routing can retrieve it and pass it to the browser
      const phpSessionCookie = dao.getPhpSessionCookie()
      // we note in the store that the JSON session has started and store the PHP session cookie
      context.store.commit('replace', { jsonSessionStarted: true, phpSessionCookie, userAuthenticated: false, adminData: '' })
      // save the store to the [Nuxt] session
      const session = context.app.$session()
      session.save(context)
      // Return the result
      return { result: "[success]. The JSON session remains initialized but you are no longer authenticated." }
    } catch (e) {
      // log
      console.log('[end-session asyncData error=]', e)
      // report the error
      return { result: '[failure]', showErrorLoading: true, errorLoadingMessage: e.message }
    } finally {
      // log
      console.log('[end-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.