Home Server-Side Swift Tutorials

Getting Started With Vue and Vapor

Vue.js provides declarative rendering and a component system to simplify building interactive web apps. In this tutorial, you’ll learn how to add a Vue frontend on top of a Vapor and Swift backend.

5 / 5 1 Rating

Version

  • Swift 5, macOS 10.15, Xcode 12

Vue.js, or Vue, is a Frontend JavaScript Framework. While regular HTML and JavaScript projects require a lot of hardcoding or JavaScript for dynamic interactions, Vue provides declarative rendering and a component system to simplify building interactive websites.

In this tutorial, you’ll learn the basics of Vue by creating a guessing game. The game shows the user five programming article titles from around the internet and asks them to guess which is from raywenderlich.com.

A pre-built Vapor backend will gather the article titles and provide the game logic. You’ll focus on building a frontend app using Vue.

Note: This tutorial assumes you know the basics of HTML, CSS and JavaScript. You’ll need an editor, such as Atom, VSCode or WebStorm, for your Vue app. Each of these editors has plugins that make it easy to create and edit Vue component files.

Finally, you need Xcode 12, Swift 5.3 or newer and the Vapor Toolbox to run the Vapor backend. If you’re new to Server-Side Swift and Vapor, check out Getting Started with Server-Side Swift with Vapor 4.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial.

It’s recommended you install Vue with npm, or Node Package Manager. npm is a package manager for JavaScript projects. You can learn more here.

Open Terminal to install the necessary tools. Feel free to skip the steps for installing any software you already have.

First, you’ll install NPM.

Installing NPM

nvm is a tool for installing and managing different Node.js versions. It lets you install Node modules globally without requiring admin privileges.

Run the following command to install nvm:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash

Close and then open Terminal. Install npm by running:

nvm install node

Note that installing the NodeJS tools can take a while to complete.

Next, you’ll install the Vue CLI.

Installing the Vue CLI

With Node installed, you can use npm to manage your web apps’ dependencies. In this tutorial, you’ll use Vue’s official CLI, or Command Line Interface, to set up your project. It’s like Vapor Toolbox, but for Vue!

In Terminal, run the following to install the Vue CLI:

npm install -g @vue/cli

Here:

  • npm install installs a package.
  • -g specifies that the package will install globally on your system, not just in the current project.
  • @vue/cli will install the Vue CLI from the npm repository, https://npmjs.com.

Now you’re ready to create your project!

Creating a Project

With Vue CLI installed, navigate to a directory using the command line where you’ll create your new project:

mkdir my-project
cd my-project

Then, run the following command to create a project:

vue create rw-guesser

When prompted, select Vue 3. You can also select npm as your package manager if prompted. The CLI will download all of the necessary tools and dependencies, then create a project in a new directory named rw-guesser.

With the project set up, run these commands:

cd rw-guesser
npm run serve

The first command takes you into the project directory created by the Vue CLI. The second command starts a dev server, watches for any changes in your codebase and rebuilds your code. Most of the time, it can also reload the website. The tool shows the address where you’ll find the project, so you don’t need to guess!

Open the website. You’ll see the following page:

vue-default-screen

Installing Bootstrap and Axios

When developing a web app, CSS code helps you style your app’s appearance. A variety of libraries make this easy. In this tutorial, you’ll use Bootstrap, a framework that provides many out-of-the-box tools to help you quickly design and build responsive websites.

Additionally, you’ll use axios as an HTTP client, giving you a high-level API to make HTTP calls from your frontend app to your Vapor backend.

Install the packages with npm by running:

npm install axios bootstrap

Your project now has all of its dependencies!

Project Structure

Before you begin, review your project’s folder structure.

When working with npm-based projects, node_modules contains your dependencies.

package.json contains your project manifest, listing all dependencies. As you may have guessed, package-lock.json locks onto specific versions of dependencies.

When the Vue CLI set up your project, it also created several directories.

public contains the basic HTML boilerplate of this template. Open index.html to see how it’s set up by default in Vue projects. The most important aspect to notice is its body, which contains a main app div tag with an id set to "app".

src contains your JavaScript files and Vue components. To understand the basics of how a Vue app works, open main.js in src.

Notice a Vue app is created and mounted on the tag with an "app" id, which corresponds to the main app div you observed above. This is how Vue initializes itself on the DOM.

The default project mounts a single component called App, defined in App.vue.

Now that you know what a Vue project looks like, it’s time to start writing code!

Listing Titles

Open App.vue, which defines this project’s root component.

Vue component files have at least two sections: template and script.

The template section declaratively defines the component’s layout. You can use standard HTML and Vue components defined in other files to compose your app’s layout.

The script section defines the components’ functionality. You can import Vue components from other files and use standard JavaScript features like bootstrap imported libraries.

Notice the default App component currently imports the HelloWorld.vue component.

Understanding Components

A component provides a way to separate the functionality of a large, complex web app into smaller, reusable pieces. You create your desired functionality and visual elements by composing multiple small components into a single complex app.

Before you create your components, it’s important to understand their structure. Take a look at the following template from App.vue:

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

This code defines the component’s structure. It looks similar to how you might do it in regular HTML. But with Vue, HTML is just the beginning!

In this template, a standard HTML image tag displays the Vue logo. However, the next tag isn’t a regular HTML element. Instead, this element represents a Vue component, defined and imported from another file.

The definition of a Vue component’s template goes hand-in-hand with its JavaScript implementation.

You guessed it! It’s time to analyze the script section.

import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}

In the script section, you can import libraries or other components. This definition simply contains an import of the HelloWorld component. More importantly, this is where you export your component’s definition.

Now, it’s time to create the basic layout.

Creating a Basic Layout

First, for a clean starting point, delete the existing components directory. Then, delete all the code from the template and script sections.

Next, replace the contents of the template tag with:

<div class="container">
  <div class="col-sm-12 col-md-6 offset-md-3 offset-sm-0">
    <!-- Website Contents Here -->
  </div>
</div>

This code sets up a bootstrap layout container with various responsive formats. A bootstrap layout is 12 columns wide. Because of col-sm-12, small, or sm, display sizes will take the full 12 column width, ensuring mobile users see their screen space used effectively.

When using the same pattern, medium, or md, display sizes use only half of the available columns. Any larger display sizes implicitly use the next lowest one, such as medium for large displays.

offset-md-3 means you get three columns of spacing to the left of the content, so it appears in the center.

Implementing Functionality

Next, replace the contents of the script tag with the following code:

export default {
  name: 'App',
  components: {
    // 1
  },
  data() {
    // 2
    return {
      // The model received from Vapor. Contains an `id` and 
      // `answers` as an array of strings
      guess: Object,
      // `true` when successfully guessed, `false` when 
      // unsuccessfully guessed. Null when not guessed.
      correct: Boolean | undefined,
      // The selected guess, in the list `guess.answers`
      selected: Number | undefined,
      // The total amount of attempted guesses
      totalGuesses: Number,
      // The amount of correct guesses
      correctGuesses: Number,
      // Used to hide the UI when reloading
      updatingGuess: Boolean,
    }
  },
  methods: { // 3
    reloadGuess() {
      // 4
    },
  },
  mounted() {
    // 5
    this.totalGuesses = 0;
    this.correctGuesses = 0;
    this.correct = null;

    this.reloadGuess();
  }
}

Here’s what you added:

  1. Allows importing any external components used as part of this component. You’ll use this later.
  2. This data function defines the schema of the state that this component relies on.
  3. This methods function defines any functional implementations of this component, used by lifecycle hooks or user interaction.
  4. A specific definition of a function that will load new article titles to guess.
  5. A function that Vue calls when the component is inserted into the DOM. In this scenario, this initializes the state to default values.

Running the Backend

Now it’s time to get your backend running. Open a new Terminal window. Navigate to the Backend Vapor app contained in the downloaded tutorial materials and run:

vapor run serve

This code will compile and run the Vapor app. Backend development has never been so simple!

Reloading Guesses

As mentioned previously, you’ll use axios to load data from HTTP APIs. Before you can use axios, you have to import it. Add the following at the top of the script section in App.vue:

import axios from 'axios';

With axios imported, the script can now use this library. To load a guess from your Vapor Backend API running locally, the imported axios object has a get method to make requests. Calling this will return a Promise.

Note: If you’ve previously done asynchronous programming, Promises will feel familiar. In fact, they use many of the same concepts you may have seen in SwiftNIO. For a Swift refresher, check out SwiftNIO: A simple guide to async on the server

Continue by updating the reloadGuess implementation:

reloadGuess() {
  this.updatingGuess = true;
  
  // 1
  axios.get('http://localhost:8081/guess').then(response => {
    // 2
    this.updatingGuess = false;
    this.guess = response.data;
    this.selected = null;
    
    // 3
    setTimeout(this.hideAlert, 3000);
  });
},
hideAlert() {
  this.correct = null;
},

Here you:

  1. First, fetch the next guess object. This prompts the API to load new article titles and pass on a compilation of them.
  2. Then, once fetched, update the local state to represent the guess.
  3. Finally, after three seconds, hide the message containing an indication of success or failure for previous guesses.

Creating the Guess UI

Now that the guesses are loading, it’s time to render them in the UI. Add the following code where you find a comment marking the website’s content location:

<div>
  <!-- Add the guess success/failure message -->
  <h2>Which article is from RayWenderlich.com?</h2>
  <h3>You've guessed {{correctGuesses}} out of {{totalGuesses}} correctly!</h3>
	
  <ul class="list-group mt-5">
    <li
      :class="selected === index ? 'list-group-item active' 
        : 'list-group-item'"
      v-for="(option, index) in guess.answers"
      :key="index"
    >
      {{option}}
    </li>
  </ul>
  <!-- Add the Submit Button -->
</div>

There’s a lot going on there! Here’s a breakdown:

  • First, HTML classes don’t usually include a colon as a prefix. This colon tells Vue that you computed the class attribute from a JavaScript expression. This expression checks whether the index value matches the selected index in the component’s state.
  • Next, v-for tells Vue this element should repeat for each element in guess.answers. Since Vue needs to see each element as unique, you need a key attribute for Vue to distinguish the views. The index in the array serves as a simple and unique key.
  • Finally, you use the string in option, which is read from the answers array, in the template using a mustache notation. This renders the string as part of the HTML in that position.

Look at what you’ve created! Make sure the guesses are loading. The game will look like this:

guessing-game-basics

Sending Guesses

Nothing happens when you click any of the titles! That’s because there’s no input handling. Luckily, handling events in Vue is straightforward.

Add the following line of code to the li element above:

@click="() => selectAnswer(index)"

This registers a left-click handler on each of the list items, calling the function selectAnswer. However, there’s no definition or implementation for selecting answers yet.

As before, functional implementations belong in the methods block. Add the following implementation there:

selectAnswer(index) {
  this.selected = index;
},

Make sure to separate each of the methods by a comma. You should include a trailing comma after each object value or array element whenever your code spans multiple lines. It’s an excellent Javascript habit and common code style.

Save the file. In your reloaded browser app, click one of the titles. You’ll see it highlighted like this:

guessing-game-selection

Submitting Results

With the guess selected, it’s time to shine! Did you guess the title right? Well, if there’s no way to submit your answer, you’ll never know.

So, it’s time to implement a submit button!

Using the techniques previously learned, add the following code at the submit button mark:

<button
  type="button"
  class="btn col-sm-5 btn-primary mt-4"
  @click="() => sendGuess(selected)"
>Send Answer</button>

Now, to implement sendGuess, add the following code in the component definition:

sendGuess(index) {
  // 1
  if(!this.guess) {
    return;
  }

  // 2
  axios.patch(`http://localhost:8081/guess/${this.guess.id}`, {
    // 3
    index: index
  }).then(response => {
    // 4
    this.correct = response.data.correct;

    // 5
    this.totalGuesses += 1;
    if(response.data.correct) {
      this.correctGuesses += 1;
    }

    // 6
    this.guess = undefined;
    this.reloadGuess();
  });
},

Here’ a step-by-step breakdown:

  1. A guard statement returns immediately if the guess is null.
  2. Then a PATCH request submits the user’s choice to the Vapor backend.
  3. Encode the PATCH body with a JSON object. The object has a single key, `index` and you set the value to the guessed index.
  4. If the request was successful, the choice’s correctness is set on the state.
  5. This step updates the total and correct guess counts.
  6. Then this step removes and reloads the choices.

Save the file. If you reload your browser you’ll see the final version of your Vue app!

Where to Go From Here

You can download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

Congrats on completing this tutorial! You learned how to create a basic Vue app and connect that app to a Vapor backend.

As you may have guessed, you’ve only scratched the surface of Vue. Their official documentation provides both a fantastic reference, as well as a solid introduction to the what, how and why of Vue.

If you want to learn more about the backend code used in this tutorial, Server-Side Swift With Vapor is a great place to start. If you want to dive deeper into frontend web development, take a look at the React Framework as well.

Feel free to share your high score in the guessing game, or talk about Vue.js in the discussion below!

More like this

Contributors

Comments