Lesson 08-Nuxt.js Middleware and Plugins

Middleware

Basic Concepts of Middleware

  • Definition: Middleware consists of functions executed before a page is rendered, with access to the context object.
  • Purpose: Used for authentication, data preloading, permission control, etc.
  • Types: Global middleware, single-page middleware, and group middleware.

Creating Middleware

  • Create File: Create a middleware file in the middleware folder.
  • Write Logic: Implement the middleware logic in the file.

Example Code
Create auth.js in the middleware folder:

// middleware/auth.js
export default function ({ store, redirect }) {
  if (!store.getters['auth/isAuthenticated']) {
    return redirect('/login');
  }
}

Using Middleware

  • Global Middleware: Configure global middleware in nuxt.config.js.
  • Single-Page Middleware: Specify middleware in a page file.
  • Group Middleware: Apply middleware to a group of pages.

Example Code
Global Middleware:

// nuxt.config.js
export default {
  middleware: ['auth'],
};

Single-Page Middleware:

// pages/about.vue
export default {
  middleware: 'auth',
};

Group Middleware:

// nuxt.config.js
export default {
  router: {
    middleware: ['auth'],
  },
};

Middleware Parameters

Middleware functions receive a context object with the following properties:

  • req: Node.js request object.
  • res: Node.js response object.
  • store: Vuex store instance.
  • redirect: Redirect method.
  • app: Vue application instance.
  • route: Current route object.

Example Code

// middleware/auth.js
export default function ({ store, redirect }) {
  if (!store.getters['auth/isAuthenticated']) {
    return redirect('/login');
  }
}

Complex Logic

  • Combining Multiple Middleware: Specify multiple middleware using an array.
  • Conditional Logic: Execute different middleware based on conditions.
// nuxt.config.js
export default {
  middleware: ['auth', 'admin'],
};

Error Handling

  • Catching Errors: Capture and handle errors within middleware.
// middleware/auth.js
export default function ({ store, redirect, error }) {
  if (!store.getters['auth/isAuthenticated']) {
    error({ statusCode: 401, message: 'Unauthorized' });
  }
}

Example Project Structure

project/
├── middleware/
│   ├── auth.js
│   └── admin.js
├── pages/
│   ├── index.vue
│   ├── about.vue
│   └── login.vue
├── store/
│   └── auth.js
└── nuxt.config.js

Combining Multiple Middleware

In real-world applications, you may need to combine multiple middleware to handle complex logic, such as authenticating users and then checking their permissions.

// middleware/auth.js
export default function ({ store, redirect }) {
  if (!store.getters['auth/isAuthenticated']) {
    return redirect('/login');
  }
}

// middleware/admin.js
export default function ({ store, redirect }) {
  if (!store.getters['auth/isAdmin']) {
    return redirect('/dashboard');
  }
}

Using These Middleware in Pages or Groups:

// nuxt.config.js
export default {
  router: {
    middleware: ['auth', 'admin'],
  },
};

Dynamic Middleware

Dynamic middleware allows you to conditionally apply middleware based on certain criteria, achieved by dynamically specifying middleware names in pages or groups.

// pages/admin.vue
export default {
  middleware: ['auth', 'admin'],
};

Asynchronous Middleware

Middleware can be asynchronous, allowing you to use the await keyword to wait for asynchronous operations to complete.

// middleware/auth.js
export default async function ({ store, redirect }) {
  const isAuthenticated = await store.dispatch('auth/checkAuthentication');
  if (!isAuthenticated) {
    return redirect('/login');
  }
}

Error Handling in Middleware

Proper error handling in middleware is critical to ensure application stability and a good user experience.

// middleware/auth.js
export default function ({ store, redirect, error }) {
  if (!store.getters['auth/isAuthenticated']) {
    error({ statusCode: 401, message: 'Unauthorized' });
  }
}

Using Vuex Middleware

You can use Vuex middleware to handle more complex logic, such as modifying the Vuex store’s state within middleware.

// middleware/auth.js
export default async function ({ store, redirect }) {
  const isAuthenticated = await store.dispatch('auth/checkAuthentication');
  if (!isAuthenticated) {
    store.commit('auth/setAuthenticationStatus', false);
    return redirect('/login');
  }
}

Middleware Order

The execution order of middleware is important, as it affects the final page rendering result. Ensure middleware is defined in the correct order.

// nuxt.config.js
export default {
  router: {
    middleware: ['auth', 'admin'],
  },
};

Testing Middleware

Write tests to ensure middleware behaves as expected.

// tests/unit/middleware/auth.test.js
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import authMiddleware from '~/middleware/auth';

const localVue = createLocalVue();
localVue.use(Vuex);

describe('Auth Middleware', () => {
  let store;
  let context;

  beforeEach(() => {
    store = new Vuex.Store({
      getters: {
        'auth/isAuthenticated': () => true,
      },
    });

    context = {
      store,
      redirect: jest.fn(),
      error: jest.fn(),
    };
  });

  it('should redirect to login if not authenticated', async () => {
    store.getters['auth/isAuthenticated'] = () => false;
    await authMiddleware(context);
    expect(context.redirect).toHaveBeenCalledWith('/login');
  });

  it('should not redirect if authenticated', async () => {
    await authMiddleware(context);
    expect(context.redirect).not.toHaveBeenCalled();
  });
});

Plugins

Nuxt.js plugins are a way to extend Vue and Nuxt functionality, allowing you to run custom code when the application starts. Plugins can be used to register global components, configure third-party libraries, add custom directives, and more.

Creating Plugins

  • Create File: Create a plugin file in the plugins folder.
  • Write Logic: Implement the plugin logic in the file.

Example Code
Create axios.js in the plugins folder:

// plugins/axios.js
import Vue from 'vue';
import axios from 'axios';

// Set base URL
axios.defaults.baseURL = 'https://api.example.com/';

// Add request interceptor
axios.interceptors.request.use(
  function (config) {
    // Do something before sending the request
    return config;
  },
  function (error) {
    // Handle request errors
    return Promise.reject(error);
  }
);

// Add response interceptor
axios.interceptors.response.use(
  function (response) {
    // Do something with response data
    return response;
  },
  function (error) {
    // Handle response errors
    return Promise.reject(error);
  }
);

// Mount Axios to Vue prototype
Vue.prototype.$axios = axios;

Using Plugins

  • Register Plugin: Register the plugin in nuxt.config.js.

Example Code

// nuxt.config.js
export default {
  plugins: ['~/plugins/axios'],
};

Plugin Parameters

Plugin functions receive a context object with the following properties:

  • app: Vue application instance.
  • router: Vue Router instance.
  • store: Vuex store instance.
  • $axios: Axios instance.
  • $nuxt: Nuxt instance.
// plugins/axios.js
export default function ({ app, $axios }) {
  // Access app and $axios here
}

Global Components

  • Register Global Components: Register global components in a plugin.
// plugins/global-components.js
import Vue from 'vue';
import MyComponent from '~/components/MyComponent.vue';

Vue.component('my-component', MyComponent);

Global Directives

  • Register Global Directives: Register global directives in a plugin.
// plugins/global-directives.js
import Vue from 'vue';

Vue.directive('focus', {
  inserted(el) {
    el.focus();
  },
});

Global Filters

  • Register Global Filters: Register global filters in a plugin.
// plugins/global-filters.js
import Vue from 'vue';

Vue.filter('capitalize', function (value) {
  if (!value) return '';
  value = value.toString();
  return value.charAt(0).toUpperCase() + value.slice(1);
});

Third-Party Libraries

  • Configure Third-Party Libraries: Configure third-party libraries in a plugin.
// plugins/vuelidate.js
import Vue from 'vue';
import Vuelidate from 'vuelidate';

Vue.use(Vuelidate);

Complex Logic

  • Combining Multiple Plugins: Specify multiple plugins using an array.
  • Conditional Logic: Execute different plugins based on conditions.
// nuxt.config.js
export default {
  plugins: ['~/plugins/axios', '~/plugins/global-components'],
};

Testing Plugins

  • Write Unit Tests: Write unit tests to verify plugin behavior.
// tests/unit/plugins/axios.test.js
import axios from 'axios';
import { shallowMount, createLocalVue } from '@vue/test-utils';

const localVue = createLocalVue();

describe('Axios Plugin', () => {
  beforeEach(() => {
    localVue.prototype.$axios = axios;
  });

  it('should have a default baseURL', () => {
    expect(localVue.prototype.$axios.defaults.baseURL).toBe('https://api.example.com/');
  });
});

Example Project Structure

project/
├── plugins/
│   ├── axios.js
│   ├── global-components.js
│   ├── global-directives.js
│   └── global-filters.js
├── components/
│   └── MyComponent.vue
└── nuxt.config.js
Share your love