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-scrolltocreate 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-selectcreate 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 generateSPA
- Mode : spa is deprecated ... to change mode spa -> ssr: false
npm run buildDeploying 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 deployLaravel as a backend using JWT
composer require tymon/jwt-auth
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secretIn 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 Usernamespace 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 hashedNuxt
- 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')
}