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')
}