Mark My Words - built with Laravel and Vue.js

Laravel and Vue.js: creating the Mark My Words web app – Part 2

David Nash laravel, vue.js 2 Comments

If you missed Part 1, you can view it here. Part 1 covers installing Laravel and setting up Vue.js to search for definitions from the Oxford Dictionary API. It then displays them to the user.

Now we want to let the user save their word and a definition. While there might be multiple definitions for a word, I want to save the definition in the sense I came across it. The user can search for the word again to add a separate definition if they like.

Using Laravel to set up the database

Initially I thought that I’d just use a single table that saved the word and definition for each user. But if 1000 users each save the same word, that’s a lot of duplication. So instead, let’s normalise it! We create a lookup table called word_definitions, and another table user_words, that points to it.

We’ll use artisan to create the database migrations and the models:

php artisan make:model -m WordDefinition
php artisan make:model -m UserWord

Let’s start with the WordDefinition. All this needs to save is a word, and it’s definition. Edit database/migrations/[datetime]_create_word_definitions_table.php  so that the up() function looks like this:

public function up()
    Schema::create('word_definitions', function (Blueprint $table) {

Then for UserWord, that will simply associate a user_id with a word_id. Edit database/migrations/[datetime]_create_user_words_table.php  so that up() does just that:

public function up()
    Schema::create('user_words', function (Blueprint $table) {

Next let’s run php artisan migrate  (and quickly jump into phpmyadmin to check the tables are correct).

Great! Our tables are set up, so now let’s get some data into them.

Saving word/definition pairs for the user

Edit /routes/web.php  and add the following route:

Route::post('api/add_word', 'APIController@add_word');

Then edit /app/Http/Controllers/APIController.php  and create this function:

public function add_word(Request $request) {
    //insert word into shared table if new
    $word_def = WordDefinition::firstOrCreate([
        'word' => $request->word,
        'definition' => $request->definition

    //add word to list of user's words
    $user_word = UserWord::firstOrCreate([
        'user_id' => Auth::id(),
        'word_id' => $word_def->id

How simple is that!? The word and definition are POSTed via our route to the Laravel $request  variable. We use firstOrCreate()  to see if the word already exists. If it does, we get the ID of the WordDefinition. If it doesn’t, it gets added to the database (and again we get the ID).

Then we save it to the logged in user’s list of words, again using firstOrCreate() so that we don’t end up with duplicates.

Hooking the front end to the back end with Vue.js

In Part 1 we displayed the list of word/definitions to the user. We use the bootstrap framework list-group for each definition, like so:

<div class="list-group">
    <div v-for="item in results_list"
        class="list-group-item clearfix">
        <div class="pull-left definition">@{{ item.definition }}</div>
        <div class="pull-right word_action">@icon('add-solid', 'action')</div>

When the user clicks a definition, it calls save_definition(), so let’s add that to Vue’s methods:

save_definition: function(word) {
    // scroll to top of saved words

    var new_definition = {
        word: word.word,
        definition: word.definition,
        show: false
    //only add if it's a unique entry
    if( ! _.find(this.saved_definitions, new_definition ) ) {
        //unshift: add to top of list
        this.saved_definitions[0].show = true;
    }'/api/add_word', {
        word: word.word,
        definition: word.definition
    }).then( function(response) {

First, it uses smoothScroll to scroll to the user’s saved word list. If I’d been building this in jQuery, I wouldn’t need this. But I’m avoiding jQuery to keep things light-weight.

Then we create a new_definition object, and use the underscore.js find method to make sure we haven’t already saved this word. Instead of adding it to the end of the saved_definitions with push, we use unshift  to add it to the top. The rest of the words will be sorted alphabetically, but adding a new word to the top makes it clear that it’s been added. It also helps orient the user.

Finally, we use axios to POST to the route that we created, saving the word. We simply pass the word we got from view as the POST parameters which ends up in the Laravel $request variable, and gets saved to our database.

I think that’ll do it for Part 2 – in Part 3 (and potentially Part 4) we’ll look at deleting a definition, plus some extra little functions that improve the user experience.