Adding Laravel User Profiles

Part 37 of 48 in API Driven Development With Laravel and VueJS
Dan Pastori avatar
Dan Pastori January 11th, 2018
⚡️ Updated content is available We learned a lot since we originally wrote this article. We now have this updated for Laravel 10, Vue 3/NuxtJS 3, and Capacitor 3.

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:

php artisan make:migration added_user_profile_fields --table=users

Then add the following content to the migration:

/**
 * 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:

/*
|-------------------------------------------------------------------------------
| 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:

/*
|-------------------------------------------------------------------------------
| 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:

<?php

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:

<?php

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 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:

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:

/*
|-------------------------------------------------------------------------------
| 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:

/*
  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:

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:

/*
  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:

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

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

/*
  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:

{
  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:

<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:

<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(){
  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:

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:

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

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

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:

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:

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!

Step 8: Add Link To Profile Page

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:

<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: Helping the coffee enthusiast find their next cup of coffee. Also, helping aspiring web and mobile app developers build a single page app and converting it to a mobile hybrid. All tutorials can be found at https://serversideup.net

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

Keep Reading
View the course View the Course API Driven Development With Laravel and VueJS
Up Next → File Management with VueJS and Laravel

Support future content

The Ultimate Guide to Building APIs and Single-Page Applications with Laravel + VueJS + Capacitor book cover.

Psst... any earnings that we make off of our book is being reinvested to bringing you more content. If you like what you read, consider getting our book or get sweet perks by becoming a sponsor.

Written By Dan

Dan Pastori avatar Dan Pastori

Builder, creator, and maker. Dan Pastori is a Laravel certified developer with over 10 years experience in full stack development. When you aren't finding Dan exploring new techniques in programming, catch him at the beach or hiking in the National Parks.

Like this? Subscribe

We're privacy advocates. We will never spam you and we only want to send you emails that you actually want to receive. One-click unsubscribes are instantly honored.