Service Classes Using Plugins in Nuxt.js
By: Jake Engel
Tags: JavaScript, Vuejs, Nuxtjs, Design Patterns, Software Architecture

Photo by Max Bender on Unsplash

Architecting Nuxt applications might seem simple at first — you have your components, pages, and maybe a few plugins. However, as your app grows and the business logic becomes more complex, so does the size and structure of your app. It might not be apparent at first, but as more API calls or other integrations make their way into the codebase you’ll quickly realize how fast these issues can become unmanageable, whether that’s due to repeated code or a poorly designed structure, or lack thereof.

Let’s take for example API calls. Every application, from a simple todo app to a large-scale eCommerce platform requires HTTP requests to an external API, whether that’s one that you’ve built yourself or by a third party. In some situations, you’ll likely need to call the same API endpoint twice.

Continuing with the eCommerce platform, consider a feature where you need to display recommendations to a user based on their purchase or viewing history. Rather than only displaying it in one place, you might want to display the recommendations both on the homepage for maximum viewability, but also within their shopping cart to encourage them to purchase more products. These are two completely separate pages, but each needs to load the exact same content from the exact same API endpoint.

You could simply write out the full API call within each page component, and return the data using Nuxt’s asyncData function.

Sure, that works, but this only covers the recommendations page. What about the homepage? Are you going to copy and paste this code there as well? What happens if the API endpoint to retrieve recommendations changes? Are you going to find every instance of this URL and replace it with the new one?

Instead, let’s ensure that the code is DRY (a.k.a. don’t repeat yourself), one of the most important principles in software engineering for writing clean code. Rather than copy and pasting similar lines of code everywhere, we can use a concept called service classes, which will allow us to abstract away common logic, making it clean and reusable.

Plugin Injection

While neither Vue nor Nuxt are built with a dependency injection architecture in mind like Angular or other frameworks might be, there still remains a simple way to make service classes such as these available everywhere in your app, both for server-side rendering and on the client. In Nuxt, this concept is referred to as plugins. These are often used in 3rd party modules, but can be just as useful for structuring your own application logic. Specifically, we’ll want to look further into injecting properties into both the Vue instance and Nuxt app context, documented further down the page.

Injecting properties allows us to make functions or other values available in both the Vue components including lifecycle hooks, templates and methods, as well as in the Nuxt context, which can be accessed in other places such as the asyncData hook within pages, or within additional plugins.

Let’s take a quick look at the documentation’s injection example as a preview of the functionality that it can provide.

Super simple! The method is injected into every place you could need it — the Vue instance and Nuxt’s context included. Nuxt will also automatically prefix the injected method with a $ to differentiate it from first party methods. The documentation also reminds us that you’ll need to register your plugin within the plugins sections of your nuxt.config.js file.

With the plugin registered and $hello injected, let’s see how we could use it.

The $hello method is accessible everywhere, providing the exact same functionality without repeating code. Of course, $hello is quite literally a “Hello World!” function and wouldn’t be all that useful, but the possibilities it provides are endless.

One of the more useful features of the injection functionality, is that injected properties are also available in the VueX store, allowing them to be used within dispatched actions.

Again — not very useful without additional logic, but consider the functionality that it could provide, such as fetching and storing data from an API, dispatching UI notifications or handling form validation.

Service Classes

Forget a single method. Nuxt’s injection supports any type of property, not just functions or static values, but also entire classes.

Although our $hello method wasn’t all that useful, an entire logging service might be more-so.

Although it’s still simple, it provides an idea of the functionality that service classes could provide.

Before we can use the class methods, we’ll need to create an instance of the LoggingService and inject it as we did with the previous $hello function.

Simple enough. After you’ve registered your plugin in your nuxt.config.js the LoggingService will be available under the $logging property, just as $hello was, but this time providing multiple functions all encapsulated within a class.

Let’s put it to work.

Just as before, the LoggingService methods are accessible throughout the component — within the template, lifecycle hooks, or even the methods. No need to copy and paste console.log('[My App] .....') throughout your app anymore.

Service Classes & Dependencies

Arguably, simple logic such as logging could be imported as individual functions from your typical module, and that’s not necessarily wrong. However, this sort of structure becomes significantly more useful when your classes rely on other dependencies for their functionality.

Let’s return to the earlier eCommerce example, where the recommendations page needed to display a list of recommended products from an external API. In that example, we made use of the first-party @nuxt/axios module, which simplified our HTTP requests.

Just as we’ve injected $hello and $logging into the app context, Axios also injected its own $axios property, providing a pre-configured instance of the Axios client for use across components, plugins and VueX. Given that $axios is accessible in the context, we’re just as easily able to inject it into a service class via its constructor, making it accessible to all our class methods.

We’ll assume that when RecommendationsService is constructed (before we inject it), it will receive an instance of an Axios client as its only argument. For the sake of the example, we’ll also assume that getRecommendations() returns data in a format similar to the following:

/api/recommendations {

// array of recommendations

data: [

{ ... },

]

}

As usual, before we can call our service, we’ll have to inject it, this time by passing the $axios instance from the plugin’s app context.

Having seen the previous modifications to this plugin, and with an understanding of the app context, injecting the RecommendationsService should be fairly straightforward. The $axios instance is simply pulled from the context and passed to the constructor before the class instance is injected.

Before wrapping up, let’s take a quick look at this service in use, which is definitely more interesting than logging a few messages with our previous service.

Simply loading a list of recommendations and displaying them to the user, this component offers two methods for accessing the RecommendationsService — either via the asyncData hook, requiring that the data be loaded before the page renders, or via our reloadRecommendations method, which can be called by clicking the button in the template. Although each method accesses the service slightly differently, they both work all the same.

Want to display you recommendations on another page? No need to rewrite the Axios API call when you can simply refer back to the service class.

One last thing to point out about our Axios dependency within the RecommendationsService class! You could simply import Axios directly from its module, but keep in mind that doing so would create a new client with the default configuration — not including any modifications (such as a base URL) that the @nuxt/axios plugin has already configured, which are only available via the instance injected into the app context.

This might not work as you’d expect…

Just as we injected Axios — any other dependency could be injected including the VueX $store or even the $http HTTP client provided by the @nuxt/http module.

Wrapping Up

As you continue to build out your Nuxt apps, the need for decoupling logic and creating some sort of service pattern will quickly become apparent.

There’s various ways to structure such an architecture, but one of the great (but sometimes not so great!) features of Nuxt is that it’s not very opinionated, allowing you to design your app the way that works best for your use cases.

You might create a new folder for your services, or even wrap all of your API services in a single object, for example, making the recommendations and logging services accessible at app.$api.recommendations and app.$api.logging respectively to prevent polluting the context too much.

Check out the code samples here, and stay tuned for a future article covering improved API service classes, as well as TypeScript integration.