Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing client-side API libraries for Nuxt.js

This is more of an architecture/design question rather than "why doesn't this work".

I'm writing a Nuxt.js SSR web app which is powered by an external API (ExpressJS). The API is responsible for authentication (via nuxt-auth - JWT) and retrieving user data etc. As I understand it, the SSR code sets a cookie on the client and uses it to retrieve the JWT in order to authenticate with the API server, on behalf of the browser.

I've written some helper methods for each model in the database, like so (simplified):

// services/UserService.js
export default class UserService {
  constructor(ctx) {
    this.$axios = ctx.$axios
  }

  getUsers () {
    return this.$axios.get('/api/users')
  }

  createUser (params) {
    return this.$axios.post('/api/users', params)
  }

  updateUser (params) {
    return this.$axios.put('/api/users/' + params.id, params)
  }

  getUser (id) {
    return this.$axios.get('/api/users/' + id)
  }
}

This works nicely for SSR because I can do this in my Nuxt pages:

<template>{{user.display_name}}</template>

<script>
import UserService from '@/services/UserService'

export default {
  asyncData ({ params, $axios }) {
    const api = new ApiService({ $axios })

    return api.Users.getUser(params).then(user => {
      return { user, api } // this makes them available in the vue instance
    })
  }
}
</script>

Now, once this has been rendered and sent to the browser, there might be a form below which is reactive. When that form is submitted by the user to update their profile, I suddenly don't have access to the UserService instance.

This is because it's defined inside the asyncData call, which can only be used within the SSR context.

I fixed this by returning api from the asyncData method as well as user, which Nuxt then puts onto the vue instance. However, this feels very wrong. Is this the correct way to do it? How are you supposed to do this? Or aren't you?

This feels especially bad since Vue will watch everything in data, and I really do not need it to keep track of the api instance...

I'm pretty new to Vue and Nuxt so if I've overlooked something please say, but my question is:

How should I design this to work both in SSR context and browser?

Thanks!

like image 310
developius Avatar asked Feb 15 '18 05:02

developius


1 Answers

I have spent some more time on this and have come up with a solution.

I've created a plugin which handles the construction of the api object and then adds it to the Nuxt context, which is available to both the SSR code and browser:

// plugins/api.js

import ApiService from '@/services/ApiService'

export default (ctx, inject) => {
  const api = new ApiService({ $axios: ctx.app.$axios })
  ctx.$api = api
  inject('api', api)
}

// nuxt.config.js

module.exports = {
  plugins: [
    '~/plugins/global.js',
    '~/plugins/datetimepicker.js',
    '~/plugins/axios.js',
    '~/plugins/api.js' // our plugin
  ],
  modules: [
    'bootstrap-vue/nuxt',
    '@nuxtjs/axios',
    '@nuxtjs/auth',
  ]
}

Now my pages are much cleaner:

<template></template>
<script>
export default {
  asyncData ({ params, $api }) {
    return $api.Users.getUser(params).then(user => {
      return { user }
    })
  }),

  watch {
    someValue (newVal) {
      // run in the browser
      this.$api.Users.doSomethingWith(val)
    }
  }
}
</script>

I'm not getting any code smells from this but if there's a Nuxt wizard reading this and spots something, please let me know!

like image 162
developius Avatar answered Oct 16 '22 04:10

developius