Let’s build a Blog: Login & Register frontend.

Conway
7 min readOct 22, 2018

--

Greetings developers. We have finished the main aspects of our serve-side architecture, it is now time for us to start building the front-end of our application. We will be using Nuxt.js to create our Login & Registration forms, to allow us to allow users to login and create accounts.

Table of Contents

The first thing we need to do is make sure that we have CORS enabled on our API. To do this open backend/config/custom.js and within the cors property add the following:

allRoutes: true, // Apply CORS to every route in the api by default.
allowOrigins: 'http://localhost:3000', // Allow requests from nuxt
allowCredentials: true, // Allow cookie sharing within CORS requests

The next thing I did was create a inexistantUser custom response so that if a user is not found we can catch the error and output errors onto our forms. To do this, create the file backend/api/reposnes/inexistantUser.js and add the following:

module.exports = function inexistentUser(message) {

const res = this.res;

// Set result message
let result = {
status: 401,
// Set custom message if it is passed into the response
message: message ? message :
'The details provided does not match any user registered.'
};

// Send JSON as that's what we want!
return res.status(result.status).json(result);

};

Within our check and login action we can now use the custom response to throw inexistantUser as an exit:

exits: {
... other exits
inexistentUser: {
description:
'The details provided does not match any user registered.',
responseType: 'inexistentUser',
},
},
fn: async function (inputs, exits) {
.... other code

// if the user cannot be found throw a bad combo
if(!user)
throw 'inexistentUser';

...... rest of code
}

Now we can run sails lift to start our server, with our server up we will need to configure our applications store. The store is a container that will hold our application state. Nuxt.js implements Vuex into its core, which allows us to be able to add our satte modules into the store folder to be read by Nuxt and loaded automatically.

Firstly we need to set a base URL for Axios to do this open frontend/nuxt.config.js and add baseURL: ‘http://localhost:1337' to the axios parameter.

To be able to use modules within our store we need to add the file frontend/store/index.js . Instead of creating a store instance, it is required to export the state as a function, and the mutations and actions as objects.

// Store state
export const state = () => ({});
// Store Actions
export const actions = {};
// Store Mutations
export const mutations = {};

Next we will create the account module to do this, create the file frontend/store/account.js and add the following:

// Account state
export const state = () => ({
user: null,
});

// Account actions
export const actions = {
USER_LOGIN({commit}, data) {
// Send request to log in the user
return this.$axios.put('api/v1/user/login', data)
.then(res => {
// Mutate the user state with logged in user
commit('SET_USER', res.data);
return res;
});
},

USER_REGISTER({commit}, data) {
// Send a request to register the user
return this.$axios.post('api/v1/user/create', data)
.then(res => {
// Mutate the user state with newly registered user
commit('SET_USER', res.data);
return res;
});
}
};

// Account mutations
export const mutations = {
SET_USER(state, user) {
// Mutate the user state
state.user = user.id ? user : null;
}
};

Now that we have configured our store we can create our log in page. With Nuxt. js we can automatically generate vue-router configuration from the file tree of Vue files within the pages directory, for example the following file tree:

Will automatically generate the following configuration.

So for us to generate the login route we need to create the file frontend/pages/login.vue and rontend/pages/account.vue. Within login.vue we need to create the template for the forms as well as the data parameters for the input binding and the methods to submit the forms. I have also added error handling so that we can display errors with the form. For the account.vue page add whatever you wish for now. Within login.vue add the following:

<template>
<section class="container">
<form id="login" @submit.prevent="submit('login')">
<div v-if="errors.login">
<strong>Danger!</strong> {{ errors.login }}
</div>

<label for="login__email">Email Address</label>

<input id="login__email"
v-model="login.emailAddress"
type="email"
required>

<label for="login__password">Password</label>

<input id="login__password"
v-model="login.password"
type="password"
required>

<button type="submit">Submit</button>
</form>

<form id="register" @submit.prevent="submit('register')">
<div v-if="errors.register">
<strong>Danger!</strong> {{ errors.register }}
</div>

<label for="register__email">Email Address</label>

<input id="register__email"
v-model="register.emailAddress"
type="email"
required>

<label for="register__full-name">Full Name</label>

<input id="register__full-name"
v-model="register.fullName"
type="text"
class="form-control"
required>

<label for="register__password">Password</label>

<input id="register__password"
v-model="register.password"
type="password"
class="form-control"
required>

<button type="submit" >Submit</button>
</form>
</section>
</template>

<script>

export default {
name: "Login",
data() {
return {
// Set login form data
login: {
emailAddress: '',
password: '',
},
// Set register form data
register: {
emailAddress: '',
fullName: '',
password: '',
},
// Set form errors
errors: {
login: '',
register: ''
}
}
},
methods: {
submit(form) {
const _this = this;
_this.$store
.dispatch('account/USER_' + form.toUpperCase()
, this[form])
.then(res => {
if (res.status === 200)
_this.$router.push('/account');
})
.catch(error =>
_this.errors[form] = error.response.data.message
);
}
}
}
</script>

This will now generate the views for the login form. If you now goto http://localhost:3000/login we will be able to use the form to login and create the user. The user will then be redirected to the account template you created, although if you were to refresh the page the user will not be set in your store.

We are required to utilise the session cookies that are set within the API, to do this we are required to use the APIs’ user.check action to send the user data sever-side to client-side, to do this we need to use the nuxtServerInit action within our store. First we need to add a GET_USER action to our account store, within frontend/store/account.js add the following:

GET_USER({commit}) {
// Send request to check user against cookie
return this.$axios.get('api/v1/user/check')
.then(res => {
// Mutate the user state with logged in user
commit('SET_USER', res.data);
return res;
})
.catch(error => {
// Mutate the user state to null
commit('SET_USER');
return error;
})
},

Next we need to add the nuxtServerInit action, open up the file frontend/store/index.js and add the replace the actions with the following:

// Store Actions
export const actions = {
nuxtServerInit({dispatch}, {req}) {
return new Promise((resolve, reject) => {
// Reset Axios default headers
this.$axios.defaults.headers.common = {};
// Match Axios default headers with request headers
Object.keys(req.headers).map((key) => {
this.$axios.defaults.headers.common[key] = req.headers[key]
});
// dispatch GET_USER
dispatch('account/GET_USER')
.then(res => {
resolve(true)
})
.catch(error => {
console.log('Provided token is invalid:', error);
resolve(false)
});
});
},
};

What this will do is reset the Axios deafult headers with the headers within the request so that we are able to use the client side cookies server side. Without that there will be no cookies sent to the server so it will always return that the user was not found.

Our only issue now is that a guest user will still be able to go to the account page even if they are not logged in. To prevent this we need to create an isAuthenticated Getter for our account as well as Middleware to check if the application store to see if the user is authenticated. To do this within frontend/store/account.js add the following:

// Account getters
export const getters = {
isAuthenticated(state) {
return !!state.user
},
loggedUser(state) {
return state.user
}
};

And the you will need to create the file backend/middleware/authenticated.js and add the following:

export default function ({store, redirect, route}) {
// Get isAuthenticated getter from account store
const isAuthenticated = store.getters['account/isAuthenticated'];
// Test route to see if is login page or admin page.
const isAdminUrl =
/^\/admin|\/account(\/|$)/.test(route.fullPath);
const isLoginUrl = /^\/login(\/|$)/.test(route.fullPath);

// If user is authenticated and is login url
if (isAuthenticated && isLoginUrl)
// Redirect to account page
return redirect('/account');

// If user is not authenticated and is an admin url
else if (!isAuthenticated && isAdminUrl)
// Redirect to login page
return redirect('/login');

// Resolve promise
return Promise.resolve()
}

We are then required to add router config to frontend/nuxt.config.js this looks like the following:

router: {
middleware: 'authenticate'
},

Now if we were to open the login page as a logged in user it would redirect to the account page and if the guest was trying to access a url under account or admin it would redirect to the login page.

Conclution

Now we have the capiblitiues for a user to log into the site, check if they are logged in server-side and the functionality that will authenticate the routes and see if a user is logged in. Next we can actually start building the views to be able to create update and delete our models!

I would recommend pulling the series GitHub Repository as I have commented throughout the code. You can also follow my progress on Instagram and on Twitter.

--

--