Learning Nuxt - Nuxt JS with Laravel API

This is the course from Udemy - https://www.udemy.com/course/laravel-nuxt-vue/ Nuxt JS with Laravel API - Building SSR Vue JS Apps

Vue Way

import axios from 'axios'
export default {
  data(){
    return {
      posts: []
    }
  },
  mounted() {
    axios.get('https://jsonplaceholder.typicode.com/posts')
    .then(response => {
      console.log(response.data)
      this.posts = response.data
    })
    .catch(error => {
      console.log(error)
    })
  }
}

Nuxt Way

import axios from 'axios'

  export default {
    components: {
      Card
    },
    data() {
      return {
        posts: ''
      }
    },
    async asyncData() {
      let {data} = await axios.get('https://jsonplaceholder.typicode.com/posts')
      return { posts: data }
    }

This sets the data to the component

async asyncData() {
      let res = await axios.get('https://jsonplaceholder.typicode.com/posts')
      return { posts: res.data }
    }

Destructur ?

async asyncData() {
      let {data} = await axios.get('https://jsonplaceholder.typicode.com/posts')
      return { posts: data }
    }

my note : need to familiarize with this concept destructur ?

Vue Component

make new file Card.vue

<template>
  <div class="card bg-light mb-3" style="max-width: 18rem;">
    <div class="card-header">Post: {{post.id}}</div>
    <div class="card-body">
      <h5 class="card-title">{{post.title}}</h5>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    post: Object
  }
}
</script>

in the page

<Card v-for="post in posts" v-bind:key="post.id" :post="post" class="ml-auto mr-auto"></Card>
import Card from '@/components/Card'


  export default {
    components: {
      Card
    },
    data() {
      return {
        posts: ''
      }
    },
    async asyncData() {
      let {data} = await axios.get('https://jsonplaceholder.typicode.com/posts')
      return { posts: data }
    }
  }

Show post by id

To show the params of the id

{{ $route.params.id }}

in Card.vue

<nuxt-link :to="{name : 'posts-id' , params: {id: post.id}}" >
   {{post.title}}
 </nuxt-link>

my note : every route has a name, posts-id is the name of the route. You can find the names in .nuxt/router.js

Store - Vuex

// create a store
export const state = () => ({
  posts: {}
});

// getters
export const getters = {
  posts(state) {
    return state.posts
  }
}

// mutations
export const mutations = {
  SET_POST(state, posts) {
    state.posts = posts
  }
}

// actions
export const actions = {
  setPosts({commit}, posts) {
    commit("SET_POSTS", posts)
  }
}

<template>
  <div class="container">
    <div>
      <h2>Making API request - the vue way</h2>
    </div>
    <div class="container row">
      <Card v-for="post in allPosts" v-bind:key="post.id" :post="post" class="ml-auto mr-auto"></Card>
    </div>
  </div>
</template>

<script>
  import axios from 'axios'
  import Card from '@/components/Card'

  export default {
    components: {
      Card
    },
    data() {
      return {
        posts: ''
      }
    },
    computed: {
      allPosts() {
        return this.$store.getters.posts
      }
    },
    head: {
      title: 'List of posts'
    },
    async asyncData({store}) {
      let {data} = await axios.get('https://jsonplaceholder.typicode.com/posts')
      store.dispatch('setPosts', data)
    }
  }

</script>

Another Way

import {mapGetters} from 'vuex'
...mapGetters(['posts'])

my note : in loop need to change to computed method name. To access store add {store} then store.dispatch('setPosts', data)

Plugins

Example

npm i vue-scrollto

create plugins/scrollto.js

import Vue from 'vue';
import VueScrollTo from 'vue-scrollto';

Vue.use(VueScrollTo);

in nuxt.config.js

plugins: ["@/plugins/scrollto.js"]

in html

<button class="btn btn-danger" v-scroll-to="'body'">Back to Top</button>

Some plugin can't be used in spa, example below

npm i vue-select

create plugins/vueselect.js

import Vue from 'vue';
import vSelect from 'vue-select';

Vue.component('v-select', vSelect);

in nuxt.config.js

plugins: ["@/plugins/scrollto.js",
  {
    src: "@/plugins/vueselect.js",
    ssr: false
  }
  ],

in html

<no-ssr>
	<v-select v-model="selected" placeholder="Select Category" :options="['foo','bar']"></v-select>
</no-ssr>

my note : to make this work need to add ssr: false

nuxtServerInit

This will load when the server is up

in store.js

export const actions = {
  async nuxtServerInit({commit}) {
    let {data} = await axios.get('https://jsonplaceholder.typicode.com/posts')
    commit("SET_POSTS", data)
  }
}

Applying Transition g

nuxt.config.js

transition: {
    name:"fade",
    mode: "out-in"
},
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

Deploying to Firebase Hosting

Static page

  • You won't be able to use Async since it needs a server
npm run generate

SPA

  • Mode : spa is deprecated ... to change mode spa -> ssr: false
npm run build

Deploying static / spa to firebase

  • After running one of the command on top
sudo npm install -g firebase-tools
firebase login
firebase init # choose hosting only, choose folder dist
firebase deploy

Laravel as a backend using JWT

composer require tymon/jwt-auth

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

php artisan jwt:secret

In user.php

use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

public function getJWTCustomClaims()
    {
        return [];
    }
}

In config/auth.php

'defaults' => [
        'guard' => 'api', # change to this
        'passwords' => 'users',
    ],

'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'jwt', # change to this
            'provider' => 'users',
            'hash' => false,
        ],
    ],

To make resource for user

Laravel

php artisan make:resource User
namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class User extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'name' => $request->name,
            'email' => $request->email,
            'created_at' => $request->created_at,
        ];
    }
}
use App\Http\Resources\User as UserResource;

return new UserResource($user)

# if need to add another

return (new UserResource($user))->additional([
            'meta' => [
                'token' => $token
            ]
        ]);

I'm using laravel 8, shows error "Undefined method 'attempt'"

$token = auth()->attempt($request->only('email', 'password'))
# if it's false it might be the password is not hashed

Nuxt

  • create an empty store/index.js

store/auth.js

export const getters = {
  authenticated (state) {
    return state.loggedIn
  },
  user (state) {
    return state.user
  }
}

nuxt.config.js


  auth: {
    strategies: {
      local: {
        endpoints: {
          login: {
            url: '/login', method: 'post', propertyName: 'meta.token'
          },
          logout: { 
            url: '/logout', method: 'post'
          },
          user: { 
            url: '/user', method: 'get', propertyName: 'data'
          }
        }
      }
    }
  },

// Modules (https://go.nuxtjs.dev/config-modules)
  modules: [
    // https://go.nuxtjs.dev/axios
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],

  // Axios module configuration (https://go.nuxtjs.dev/config-axios)
  axios: {
    baseURL: 'http://localhost:8000/api'
  },

Login


<template>
  <div class="container">
    <div class="col-md-6 mt-5">
      <h2>Login</h2>
      <br>
      <form class="text-left" @submit.prevent="submit">
        <div class="form-group">
          <label>Email address</label>
          <input
            v-model.trim="form.email"
            type="email"
            class="form-control"
            autofocus
          >
          <small class="form-text text-danger">Show errors here</small>
        </div>
        <div class="form-group">
          <label>Password</label>
          <input v-model.trim="form.password" type="password" class="form-control">
          <small class="form-text text-danger">Show errors here</small>
        </div>
        <button type="submit" class="btn btn-primary">
          Login
        </button>
      </form>
      <br>
      <p>
        Don't have an account?
        <nuxt-link to="/register">
          Register
        </nuxt-link>
      </p>
    </div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      form: {
        email: '',
        password: ''
      }
    }
  },
  methods: {
    async submit () {
      try {
        await this.$auth.loginWith('local', {
          data: this.form
        })
          .then(function (response) {
            this.$router.push('')
          })
      } catch (e) {
      }
    }
  }
}
</script>

Create Logout

<a @click.prevent="logout" class="nav-link">Logout</a>

<script>
export default {
  methods: {
    logout () {
      this.$auth.logout()
    }
  }
}
</script>

Register


<template>
  <div class="container">
    <div class="col-md-6 mt-5">
      <h2>Register</h2>
      <br>
      <form @submit.prevent="submit" class="text-left">
        <div class="form-group">
          <label>Full Name</label>
          <input
            v-model.trim="form.name"
            type="text"
            class="form-control"
          >
          <small class="form-text text-danger">Show errors here</small>
        </div>
        <div class="form-group">
          <label>Email address</label>
          <input
            v-model.trim="form.email"
            type="email"
            class="form-control"
            autofocus
          >
          <small class="form-text text-danger">Show errors here</small>
        </div>
        <div class="form-group">
          <label>Password</label>
          <input
            v-model.trim="form.password"
            type="password"
            class="form-control"
            autofocus>
          <small class="form-text text-danger">Show errors here</small>
        </div>
        <button type="submit" class="btn btn-primary">
          Register
        </button>
      </form>
      <br>
      <p>
        Don't have an account?
        <nuxt-link to="/register">
          Register
        </nuxt-link>
      </p>
    </div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      form: {
        email: '',
        name: '',
        password: ''
      }
    }
  },
  methods: {
    async submit () {
      try {
        await this.$axios.$post('register', this.form)
        await this.$auth.loginWith('local', {
          data: {
            email: this.form.email,
            password: this.form.password
          }
        })
          .then(function (response) {
            this.$router.push('')
          })
      } catch (e) {
      }
    }
  }
}
</script>

Plugins

Create Global mixin

  • the purpose this is so to not keep ongetting mapgetters in every file

plugins/mixins/user.js

import Vue from 'vue'
import { mapGetters } from 'vuex'

const User = {
  install (Vue, options) {
    Vue.mixin({
      computed: {
        ...mapGetters({
          user: 'auth/user',
          authenticated: 'auth/authenticated'
        })
      }
    })
  }
}

plugins/axios.js

export default function ({ $axios, store }) {
  $axios.onError((error) => {
    if (error.response.status === 422) {
      store.dispatch('validation/setErrors', error.response.data.errors)
    }
    return Promise.reject(error)
  })

  $axios.onRequest(() => {
    store.dispatch('validation/clearErrors')
  })
}

Validation

store/validation.js

export const state = () => ({
  errors: {}
})

export const getters = {
  errors (state) {
    return state.errors
  }
}

export const mutations = {
  SET_VALIDATION_ERRORS (state, errors) {
    state.errors = errors
  }
}

export const actions = {
  setErrors ({ commit }, errors) {
    commit('SET_VALIDATION_ERRORS', errors)
  },
  clearErrors ({ commit }) {
    commit('SET_VALIDATION_ERRORS', {})
  }
}

plugins/mixins/validation.js

import Vue from 'vue'
import { mapGetters } from 'vuex'

const Validation = {
  install (Vue, options) {
    Vue.mixin({
      computed: {
        ...mapGetters({
          errors: 'validation/errors'
        })
      }
    })
  }
}

Vue.use(Validation)

Don't forget to add it to config

// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
  plugins: [
    './plugins/mixins/user.js',
    './plugins/axios.js',
    './plugins/mixins/validation.js'
  ],

Middleware

middleware/guest.js

export default function ({ store, redirect }) {
  if (store.getters['auth/authenticated']) {
    return redirect('/profile')
  }
}

middleware/clearValidation.js

export default function ({ store }) {
  store.dispatch('validation/clearErrors')
}

Subscribe to You Live What You Learn

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe