DEV Community

Cover image for Nuxt like a pro. Use service pattern (My Best practice)
Radonirina Maminiaina
Radonirina Maminiaina

Posted on • Updated on

Nuxt like a pro. Use service pattern (My Best practice)

Intro

We all know that NuxtJS is an awesome framework. Unlike Angular which has a well structured folder for services, nuxt doesn't. But like every good nuxt developer, using services are essential, especially when you use VueX with NuxtJS.

Implementation of services in Nuxt

Before we implement services, make sure you have vuex, nuxtjs/axios installed in your project.

Into the plugins directories, create a new file (e.g: service.js)

export default ({$axios}, inject) => {
  inject('getProductList', async (params) => await $axios.get('cool_get_url', {
    params
  }))
  inject('createProduct', async (data) => await $axios.post('cool_post_url', data))
  // ... and so on
}
Enter fullscreen mode Exit fullscreen mode

So, in our Nuxt Component, we can access those service within the context this.

<template>
  <div><!-- Make a liste of product--></div>
</template>

<script>
  export default {
    name: 'MyAwesomeProduct',
    async mounted() {
      // accessing $getProductList service using this
      const { data } = this.$getProductList({type: 'awesome type'})

      // accessing $createProduct service
      const { data } = this.$createProduct({
        name: 'awesome product'
      })

    }
  }
</script>
Enter fullscreen mode Exit fullscreen mode

Or if your system is more complicated, and you use vuex, you can access those service within vuex store as well.

export default {
  // some basic store configuration
  actions: {
    async getProductList({ commit }) {
      const { data } = this.$getProductList({type: 'awesome type'})
      // data manipulation before commit ...
      commit('MY_AWESOME_MUTATION', data)
    },
    async createProduct({ commit }) {
      const { data } = this.$createProduct({type: 'awesome type'})
      // data manipulation before commit ...
      commit('MY_AWESOME_MUTATION', data)
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

And within your component, you can use MapGetters to retrieve data after calling the action using MapActions

Let refactor the code, My Best Practice

Now, time is coming, so let rewrite those services.
In the root of your project, you can create a folder called services, and within that folder, create a new file named product.js (If you like, you can prefix it with service 😉 ). Here the content of that file:

export default ($axios) => {
  return {
     getProductList: async  (params) => {
        return await $axios.get('cool_get_url', {
           params
        })
     },
     createProduct: async  (data) => {
        return await $axios.post('cool_post_url', data)
     }
     // more verbs and more actions
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, the file [service.js] within the plugins will look like this:

import productService from '~/services/product'

export default ({ $axios }, inject) => {
  const allMethods = {
    ...productService($axios),
    // import another service here
  }
  const methods = Object.keys(allMethods)
  methods.forEach((method) => {
    inject(method, allMethods[method])
  })
}

Enter fullscreen mode Exit fullscreen mode

We know that Object.keys(allMethods) will return the keys of allMethods, and then, we use those key, as the name of our services, and allMethods[method] will return the methods within the services.
Now inject will contains small code, and it's more clear.
Our services are separated in new file.

Pros

  • Separation of concern
  • Clean code
  • Easy for e2e testing
  • Easy for unit testing

Cons

  • More files to handle
  • (Put in comments if you find another cons with this approach)

Now for deployment you can read this article which is about optimisation.

Note: I repeat it again, it's my own best practice that I use for any of our projects. If you find it usefull, feel free to use it. 😇

Top comments (18)

Collapse
 
bacodekiller profile image
Quang Hiep • Edited

how to use it with Interceptors axios for refresh token when "token is invalid"? And save token, refresh token to cookies?

Collapse
 
radonirinamaminiaina profile image
Radonirina Maminiaina

You can use interceptor and in onResponse you can check if your token is valid or not.

Collapse
 
bacodekiller profile image
Quang Hiep

yes. when refresh token success but should I save token to vuex or cookies? thanks.

Thread Thread
 
radonirinamaminiaina profile image
Radonirina Maminiaina

The simplest way is using localstorage to save your token.

Thread Thread
 
bacodekiller profile image
Quang Hiep

But localStorage is client-side. How can i save it from server nuxtjs?

Thread Thread
 
radonirinamaminiaina profile image
Radonirina Maminiaina

You can use this lib and use cookie for saving the info within nuxt server

Thread Thread
 
bacodekiller profile image
Quang Hiep

can you give an example using lib or project using lib? tks you

Thread Thread
 
radonirinamaminiaina profile image
Radonirina Maminiaina

You can see this doc

Collapse
 
raphaelnikson profile image
Raphael Nikson • Edited
import roles from '@/services/roles'
import users from '@/services/users'

export default ( { $axios }, inject) => {

    let services = {
        roles: {
            ...roles($axios)
        },
        users: {
            ...users($axios)
        }
    }

    const methods = Object.keys(services)

    methods.forEach((name) => {
        inject(name, services[name])
    })

}

// component 

await this.$roles.index()
await this.$users.index()

Enter fullscreen mode Exit fullscreen mode
Collapse
 
cramydev profile image
cramydev

Hello guys, I have a question. This structure works also with typescript? Or it is necessary to do some modification?

Collapse
 
radonirinamaminiaina profile image
Radonirina Maminiaina

Yes, this works with typescript

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt

I use openapi-client-axios + openapi.json (i.e. Swagger) + TypeScript (optional).

But I don't know how to inject to $axios.

Collapse
 
radonirinamaminiaina profile image
Radonirina Maminiaina

You can use nuxtjs/axios module and follow the installation intruction

Collapse
 
geminii profile image
Jimmy

It’s a great best practices that i apply on my project but i precise a prefix due to multiple services. Nice sharing 👍

Collapse
 
radonirinamaminiaina profile image
Radonirina Maminiaina

Thank you

Collapse
 
aech12 profile image
Alex Howez

Another negative is that this gets pretty overcrowded

It'd be better if you could do this.$services.user.getUsers()

Collapse
 
slidenerd profile image
slidenerd

nice post! can you add an example of how you ll test this service using vue-test-utils?

Collapse
 
lambchamps profile image
lambchamps • Edited

I like the pattern. What is your approach when runtimeconfig/environment variables is needed? I'm having a problem how composables are not available outside script setup. My bad it's for nuxt 3