API Driven Development With Laravel and VueJS (Part 36 of 38)

Adding Laravel User Profiles

Dan Pastori

January 11st, 2018

The last few tutorials, we've opened up public data for the cafes. We started opening up a few routes with larval https://serversideup.net/public-private-api-laravel/, then explored VueJS Route Security and Authentication and finally implemented some navigation guards. In this tutorial we will go through the process of

Step 1: Define Data To Collect

When building our user infra-structure I like to really tailor the content we collect to be specific to the app. This way we can expand the app to provide more value for the user. For example, if the user lives in Los Angeles, CA, they can be alerted of new cafes in the area, or if their favorite brew method is added to a cafe, they can be notified.

Other cool uses for user profiles will be to recommend cafes to friends in the future. This will start to build a coffee community. Our user profile page will allow the user to enter the following information:

  • Favorite type of coffee
  • Flavor notes
  • Whether their profile should be public/private
  • Location

Step 2: Make User Profile Migrations

The first step in the process is to add the appropriate MySQL Columns to hold the data we need. To do this, make a migration that expands the user fields:

Command to create profile migration

php artisan make:migration added_user_profile_fields --table=users

Then add the following content to the migration:

Users table migration to add profile fields

/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->text('favorite_coffee')->after('avatar');
        $table->text('flavor_notes')->after('favorite_coffee');
        $table->boolean('profile_visibility')->default('1')->after('flavor_notes');
        $table->string('city')->after('profile_visibility');
        $table->string('state')->after('city');
    });
}

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('favorite_coffee');
        $table->dropColumn('flavor_notes');
        $table->dropColumn('profile_visibility');
        $table->dropColumn('city');
        $table->dropColumn('state');
    });
}

This will add the fields we need to build a simple profile for a user and a starting point for any future additions such as a friend system.

Make sure you run php artisan migrate to add the fields!

Step 3: Add Route To Handle User Updates

Now that we have fields to update for the user, let's add the route to do just that. First, let's open up the /routes/api.php file. This contains all of our API routes. However, as of https://serversideup.net/public-private-api-laravel/, we have public and private routes. Our /api/v1/user is a public route which returns the user, and if not logged in returns an empty object. We want the update route to be behind protection so unauthorized users' requests get blocked.

In the authenticated API Routes, we will add the following route:

API Endpoint to handle profile updates

/*
|-------------------------------------------------------------------------------
| Updates a User's Profile
|-------------------------------------------------------------------------------
| URL:            /api/v1/user
| Controller:     API\UsersController@putUpdateUser
| Method:         PUT
| Description:    Updates the authenticated user's profile
*/
Route::put('/user', 'API\UsersController@putUpdateUser');

This will allow a PUT request to the /api/v1/user endpoint allowing us to add a method to update the user.

Next, we will open the app/Http/Controllers/API/UsersController.php and add the putUpdateUser() method:

Controller command to update user

/*
|-------------------------------------------------------------------------------
| Updates a User's Profile
|-------------------------------------------------------------------------------
| URL:            /api/v1/user
| Method:         PUT
| Description:    Updates the authenticated user's profile
*/
public function putUpdateUser(){

}

For now, we will leave this method alone. We will create a request validation to verify the data before finishing the method. This way we ensure the data is in the right format before updating the profile. It will also be in place so we can extend the validations if we need to add more data.

Step 4: Add Route Request Validation

We need to validate the request before we process it so let's create a new file, app/Http/Requests/EditUserRequest.php and add the following code:

Form request to add validations

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class EditUserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [

        ];
    }

    /**
     * Get the error messages for the defined validation rules.
     *
     * @return array
     */
    public function messages()
    {
        return [

        ];
    }
}

This just stubs out our request. Now with the data that we are adding in this tutorial, the only one we should validate is whether the profile_visibility is a boolean or not. Everything else is text, so whatever the user enters is fine.

Our request validation should look like:

Full validation of the edit user request

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class EditUserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'profile_visibility'    => 'sometimes|boolean'
        ];
    }

    /**
     * Get the error messages for the defined validation rules.
     *
     * @return array
     */
    public function messages()
    {
        return [
            'profile_visibility.boolean'    => 'The profile visibility flag needs to be a boolean'
        ];
    }
}

Now we can finish up our update method.

Step 5: Finish Route Update Method

Let's step back into our actual update method. Let's re-open our /app/Http/Controllers/API/UsersController.php file. On the top of the file, let's state that we will be using our EditUserRequest validation:

Use the Edit User Request we just made

use App\Http\Requests\EditUserRequest;

Now let's head back to our putUpdateUser() method. We need to inject our request validation into this method like this:

Add method to update user

public function putUpdateUser( EditUserRequest $request ){

}

Now we will have validated data and can access the data through the $request variable.

Our method should look like:

Functionality to update a user's profile

/*
|-------------------------------------------------------------------------------
| Updates a User's Profile
|-------------------------------------------------------------------------------
| URL:            /api/v1/user
| Method:         PUT
| Description:    Updates the authenticated user's profile
*/
public function putUpdateUser( EditUserRequest $request ){
    $user = Auth::user();

    $favoriteCoffee       = $request->get('favorite_coffee');
    $flavorNotes          = $request->get('flavor_notes');
    $profileVisibility    = $request->get('profile_visibility');
    $city                 = $request->get('city');
    $state                = $request->get('state');

    /*
        Ensure the user has entered a favorite coffee
    */
    if( $favoriteCoffee != '' ){
        $user->favorite_coffee    = $favoriteCoffee;
    }

    /*
        Ensure the user has entered some flavor notes
    */
    if( $flavorNotes != '' ){
        $user->flavor_notes       = $flavorNotes;
    }

    /*
        Ensure the user has submitted a profile visibility update
    */
    if( $profileVisibility != '' ){
        $user->profile_visibility = $profileVisibility;
    }

    /*
        Ensure the user has entered something for city.
    */
    if( $city != '' ){
        $user->city   = $city;
    }

    /*
    Ensure the user has entered something for state
    */
    if( $state != '' ){
        $user->state  = $state;
    }

    $user->save();

    /*
        Return a response that the user was updated successfully.
    */
    return response()->json( ['user_updated' => true], 201 );
}

What this method does is update each of the fields if there is data passed. We don't want to update the field if the user hasn't submitted any data otherwise we'd run the risk of overwriting something they previously have added.

At the end of the method we save the user and return a 201 success response with the flag that user_updated was true.

We should be good to go now and we can switch over to the VueJS portion of our single page application.

Step 6: Add Front End Functionality

We've gone through a few examples of making an API request with VueJS, so I'll post the code updates. If you need a hand, make sure you check out these articles or leave a comment below:

First, open the /resources/assets/js/api/user.js and add the following method:

Send a frontend request to the API through Axios

/*
  PUT  /api/v1/user
*/
putUpdateUser: function( public_visibility, favorite_coffee, flavor_notes, city, state ){
    return axios.put( ROAST_CONFIG.API_URL + '/user',
        {
            public_visibility: public_visibility,
            favorite_coffee: favorite_coffee,
            flavor_notes: flavor_notes,
            city: city,
            state: state
        }
    );
}

This will be our API call through Javascript to our new route. We will call this when we need to update the user.

Let's add some Vuex state, actions, getters, and mutations. Open up, /resources/assets/js/modules/users.js.

Now, let's add the following state:

Added state to Vuex

userUpdateStatus: 0

This will keep track of the update status for the user.

We then need to add the editUser() action that calls our API and submits the appropriate mutations:

Edit user Vuex model action

/*
    Edits a user
*/
editUser( { commit, state, dispatch }, data ){
    commit( 'setUserUpdateStatus', 1 );

    UserAPI.putUpdateUser( data.public_visibility, data.favorite_coffee, data.flavor_notes, data.city, data.state )
        .then( function( response ){
            commit( 'setUserUpdateStatus', 2 );
            dispatch( 'loadUser' );
        })
        .catch( function(){
            commit( 'setUserUpdateStatus', 3 );
        });
},

Now we have an action to update the user, let's add the appropriate mutations and getters.

We need to add the mutation to set the user update status so we can apply this to the UX:

Vuex mutation

/*
  Sets the user update status
*/
setUserUpdateStatus( state, status ){
    state.userUpdateStatus = status;
}

Now, we just need a getter to return the value so add:

Vuex getter

/*
    Gets the user update status
*/
getUserUpdateStatus( state, status ){
    return state.userUpdateStatus;
}

We now have everything taken care of for our front end functionality.

Let's add a page and tie it all together!

Step 7: Add Profile Page

We need to now add a form to allow the user to update their profile. For a more in depth explanation on adding front end routes, check out the following tutorial: Configuring Vue Router for a Single Page App - Server Side Up

First, let's open our /resources/assets/js/routes.js file and add the following route:

Vue Router profile update route

{
    path: 'profile',
    name: 'profile',
    component: Vue.component( 'Profile', require( './pages/Profile.vue' ) ),
    beforeEnter: requireAuth
}

Notice we have the requireAuth method we created in: Vue Router Navigation Guards with Vuex . We need to have an authenticated user to allow them to edit their profile.

Now, let's add our /resources/assets/js/pages/Profile.vue page and add the templated code:

Profile page template

<style lang="scss">
    @import '~@/abstracts/_variables.scss';

</style>

<template>
    <div id="profile" class="page">

    </div>
</template>

<script>
    export default {

    }
</script>

I'm going to do a quick over view of some of the form submission, but for more information, check out: API Driven Form Submissions with Javascript, Vuex and Laravel - Server Side Up.

We need to add a quick template that allow the user to edit their fields. I added the following:

Manage profile page

<template>
    <div id="profile" class="page">
        <div id="profile-updated-successfully" class="notification success">
            Profile Updated Successfully!
        </div>

        <div class="grid-container">
            <div class="grid-x grid-padding-x">
                <loader v-show="userLoadStatus == 1" :width="100" :height="100"></loader>
            </div>
        </div>

        <div class="grid-container" v-show="userLoadStatus == 2">
            <div class="grid-x grid-padding-x">
                <div class="large-8 medium-10 small-12 cell center">
                    <label>Favorite Coffee
                        <textarea v-model="favorite_coffee"></textarea>
                    </label>
                </div>
            </div>
            <div class="grid-x grid-padding-x">
                <div class="large-8 medium-10 small-12 cell center">
                    <label>Flavor Notes
                        <textarea v-model="flavor_notes"></textarea>
                    </label>
                </div>
            </div>
            <div class="grid-x grid-padding-x">
                <div class="large-8 medium-10 small-12 cell center">
                    <label>Visibility
                        <select id="public-visibility" v-model="profile_visibility">
                            <option value="0">Private</option>
                            <option value="1">Public</option>
                        </select>
                    </label>
                </div>
            </div>
            <div class="grid-x grid-padding-x">
                <div class="large-8 medium-10 small-12 cell center">
                    <label>City
                        <input type="text" v-model="city"/>
                    </label>
                </div>
            </div>
            <div class="grid-x grid-padding-x">
                <div class="large-8 medium-10 small-12 cell center">
                    <label>State
                        <input type="text" v-model="state"/>
                    </label>
                </div>
            </div>
            <div class="grid-x grid-padding-x">
                <div class="large-8 medium-10 small-12 cell center">
                    <a class="button update-profile" v-on:click="updateProfile()">Update Profile</a>
                </div>
            </div>
        </div>
    </div>
</template>

This template binds inputs to the appropriate data model for the field we are editing. We also have a quick loading state to show while the profile is loading and a notification when the profile has been updated successfully.

For our data we will add the fields we are editing:

Data fields we are managing

data(){
    return {
        favorite_coffee: '',
        flavor_notes: '',
        profile_visibility: 0,
        city: '',
        state: ''
    }
},

There will be a few pieces of computed data we will watch from the Vuex state:

Computed data we are watching

watch: {
    'userLoadStatus': function(){
        if( this.userLoadStatus == 2 ){
            this.setFields();
        }
    },

    'userUpdateStatus': function(){
        if( this.userUpdateStatus == 2 ){
            $("#profile-updated-successfully").show().delay(5000).fadeOut();
        }
    }
},

The userLoadStatus we watch so we can set the data to what the user is when they have been loaded. The userUpdateStatus is watched so we determine when to display the profile updated notification.

We also check, when created if the userLoadStatus is equal to 2 if it is, then we set the fields because we have enough data:

Validate the data is loaded

created(){
    if( this.userLoadStatus == 2 ){
        this.setFields();
    }
},

For our computed methods, we grab the user the userLoadStatus and the userUpdateStatus:

Computed data for our page

computed: {
    /*
        Gets the authenticated user.
    */
    user(){
        return this.$store.getters.getUser;
    },

    /*
        Gets the user load status.
    */
    userLoadStatus(){
        return this.$store.getters.getUserLoadStatus();
    },

    /*
        Gets the user update status
    */
    userUpdateStatus(){
        return this.$store.getters.getUserUpdateStatus;
    }
},

These will help us display the information we need to on the page.

For methods, we have the setFields() method which sets the data to what the user already has in their profile:

Set the fields loaded from the API

setFields(){
    this.profile_visibility = this.user.profile_visibility;
    this.favorite_coffee = this.user.favorite_coffee;
    this.flavor_notes = this.user.flavor_notes;
    this.city = this.user.city;
    this.state = this.user.state;
},

We also have an updateProfile() method which validates the information we need in our profile and dispatches the editUser action, sending all of our data through the API to be saved for the profile:

Method to update profile

updateProfile(){
    if( this.validateProfile() ){
        this.$store.dispatch( 'editUser', {
            profile_visibility: this.profile_visibility,
            favorite_coffee: this.favorite_coffee,
            flavor_notes: this.flavor_notes,
            city: this.city,
            state: this.state
        });
    }
},

The validateProfile() method for now returns true because there isn't anything we need to specifically validate. It's great to have in place for future validations to be added.

Next, we just have to add a link to the navigation for this page and we have a user profile!

Now that we have the profile page made, we need to add a link to the page. All we have to do is open up /resources/assets/js/components/global/Navigation.vue and add the following code below our avatar:

Link to the profile page with Vue Router

<router-link :to="{ name: 'profile'}" v-if="user != '' && userLoadStatus == 2" class="profile">
    Profile
</router-link>

This will add a link to the navigation to the page to edit our profile!

Conclusion

We now have user profiles added to collect some information from our users and expand up to adding friends and other possibilities. Be sure to check out all of the code here: GitHub - serversideup/roastandbrew

Of course, reach out if you have any questions and sign up for our mailing list if you are interested in more about API Driven Development: Server Side Up General List

Want to work together?

Professional developers choose Server Side Up to ship quality applications without surrendering control. Explore our tools and resources or work directly with us.

Join our community

We're a community of 3,000+ members help each other level up our development skills.

Platinum Sponsors

Active Discord Members

We help each other through the challenges and share our knowledge when we learn something cool.

Stars on GitHub

Our community is active and growing.

Newsletter Subscribers

We send periodic updates what we're learning and what new tools are available. No spam. No BS.

Sign up for our newsletter

Be the first to know about our latest releases and product updates.

    Privacy first. No spam. No sharing. Just updates.