Spring Boot with Nuxt

Introduction

I’ve been hearing a lot about Nuxt recently, so I wanted to learn more. That seemed like a good opportunity for a blog post, so here we are.

When I was a little kid, my Dad wrote me a program in BASIC for practicing math problems. When I got a problem right, there were fireworks. So in this post, we’re going to attempt to recreate that program with a Spring Boot back end and a front end created using the Nuxt framework for Vuejs.

We’ll start by setting up the REST API in Spring Boot and then move on to the front end.

The RESTful API

We’ll create the back end using Spring Boot. The application generates a simple math problem and checks the answers. There’s nothing to save, so we have no persistence layer. To keep the initial pass at the application as simple as possible, there’s also no login functionality.

We’ll start by writing a class that represents a math problem:

public class Problem {
   public enum Operation {
      ADD,
      SUBTRACT,
      MULTIPLY,
      DIVIDE
   }
    
   private int numberOne;
   private Operation operation;
   private int numberTwo;
   private int answer;
   private int userAnswer;

   // Getters and Setters

}

We’re also going to create a class for returning the correct answer and whether or not the provided answer was correct:

public class AnswerResponse {
   private int correctAnswer;
   private int userAnswer;
   private boolean correct;

   // Getters and Setters

}

Next, let’s create a service class that does the heavy lifting of generating a problem and verifying answers. We’ll annotate the class with @Service, so that Spring knows about it. For brevity, I’ve omitted the details of how the problems are being handled, but the code is available on BitBucket.

@Service
public class ProblemService {
   Logger log = LoggerFactory.getLogger(ProblemService.class);

   public Problem generateProblem() {
      Problem problem = new Problem();

      // Generate the math problem      

      return problem;
   }
   
   public int calculateAnswer(Problem problem) {
      int answer = -1;

      // Calculate the answer from the problem provided      

      return answer;
   }   
}

Now, let’s create the actual REST end points that we’ll be interacting with from our front end. We’ll annotate the class as a @RestController and we’ll set the overall @RequestMapping to /api/v1/math.

We’ll create two end points. One at /get-problem that just returns a Problem generated by the problemService.generateProblem() method. The /check-answer end point gets the correct answer from the problemService and then builds and returns the AnswerReponse.

@RestController
@RequestMapping("/api/v1/math")
public class MathGameController {
   Logger log = LoggerFactory.getLogger(MathGameController.class);
   
   @Autowired
   private ProblemService problemService;

   @GetMapping("/get-problem")
   public Problem getProblem() {
      return problemService.generateProblem();
   }
   
   @PutMapping("/check-answer")
   public AnswerResponse checkAnswer(@RequestBody Problem problem) {
      AnswerResponse response = new AnswerResponse();
      response.setUserAnswer(problem.getUserAnswer());
      int answer = problemService.calculateAnswer(problem);
      log.debug("Calculated Answer: " + answer);
      response.setCorrectAnswer(answer);
      response.setCorrect(response.getUserAnswer() == response.getCorrectAnswer());
      return response;
   }
}

If we run our application, we can access the end points using a tool like curl or Postman.

curl http://localhost:8080/api/v1/math/get-problem
{"numberOne":99,"operation":"ADD","numberTwo":21,"answer":0,"userAnswer":0}

There’s one last piece we need to enable use to support our Nuxt front end. We have to handle CORS.

We can potentially need to support a different origin depending on the environment, so we’re going to start by adding a property to application.properties for our allowedOrigin URL.

cors.allowedOrigin=http://localhost:3000

Now we need to configure the CorsRegistry in our application by specifying our CORS mappings:

@Bean
public WebMvcConfigurer corsConfigurer() {
	return new WebMvcConfigurer() {
		@Override
		public void addCorsMappings(CorsRegistry registry) {
			registry.addMapping("/api/v1/**")
               .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
               .allowedOrigins(allowedOrigin);
		}
	};
}

By default, GET, HEAD and POST options are enabled, so we specify all the methods we need to support our front end.

The Nuxt Front End

To get our front end structure set up quickly, we’ll use the create-nuxt-app command line tool.

The tool is available for npx, npm or yarn. I’m most familiar with npm, so that’s the route I chose. From the directory where we want our project, we call the command line tool:

npm init nuxt-app math-game-ui

We’ll get taken through a number of prompts, and I’ll share the choices I made below:

PromptChosenOther Options
Programming LanguageJavaScriptTypeScript
Package Managernpmyarn
UI FrameworknoneAnt Design Vue
BalmUI
Bootstrap Vue
Buefy
Chakra UI
Element
Framevuerk
Oruga
Tachyons
Tailwind CSS
Windi CSS
Vant
View UI
Vuetify.js
Nuxt.js ModulesAxiosProgressive Web App
Content – Git-based headless CMS
Linting ToolsnoneESLint
Prettier
Lint staged files
StyleLint
Commitlint
Testing FrameworknoneJest
AVA
WebdriverIO
Nightwatch
Rendering ModeSPAUniversal (SSR / SSG)
Deployment TargetStaticServer
Development Toolsjsconfig.jsonSemantic Pull Requests
Dependabot

Once we’ve gone through the create-nuxt-app configuration, it creates a stub project for us. Now we can start working on the front end for our math game.

The CLI tool created a lot of directories and files for us. We’re going to ignore many of them.

We’ll start by customizing pages/index.vue to have some basic structure and styling. We’ll also optimistically put our as yet unwritten Problem component in there as well.

There’s three major sections here and this is a common structure for Vue components. The first is the HTML and it falls between the template tags. Note the <Problem /> tag that represents our Problem component. The second section is the JavaScript. In index.vue, we just have an import for our Problem component. The final section is our styling. We have a mixture of a external stylesheets and embedded styles.

<template>
    <div id="app" class="content">
        <header>
            <h1>Math Game</h1>
            <p>This is the example math game built with Spring Boot, Vue.js and Nuxt.js</p>
        </header>
        <main>
            <Problem />
        </main>
        <footer>
            <small>Math game example app from <a href="https://amydegregorio.com">https://amydegregorio.com</a></small>
        </footer>
    </div>
</template>

<script>
import Problem from '../components/Problem';
export default {

};
</script>
<style src="../assets/css/main.css"></style>
<style src="../assets/css/animations.css"></style>
<style>
    .content {
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: stretch;
      justify-content: center;
      justify-items: center;
    }
    header {
        background-color: #166C9F;
        color: #fff;
        padding: 20px;
    }

    main {
        flex-grow: 1;
        padding: 20px;
    }

    header, main, footer {
        flex-shrink: 0;
    }

    footer {
        padding: 20px;
        background-color: hsla(202, 86%, 35%, .5);
    }
 [v-cloak] { display: none; }
</style>

Before we get to our Problem component, let’s add some configurability by creating an environment variable for our API URL. We’ll go ahead and add a .env file to our top level project directory. Next we’ll put a property in it for our API URL:

API_URL=http://localhost:8080/api/v1

Next we’ll configure our axios base URL to use the environment variable. So let’s go ahead and open up the nuxt.config.js file and add the axios configuration after the axios module section:

axios: {
    baseUrl : process.env.API_URL
},

Now that we’ve got our basic structure and axios configured, we’ll work on our Problem component. We’ll create a file named Problem.vue in our components folder.

We’re going to look at this file in pieces starting with the HTML part. As with index.vue, the HTML is wrapped in a <template> tag.

A lot of this is standard HTML, but we get extra functionality by using some of the directives the Vue.js provides us. Our buttons use the v-on directive to specify what method to call when the button is clicked. The v-if directive allows us to condition the display of a component based on the value of a variable declared in the script portion. The v-model directive is used for two-way transfer of values. The value in the variable will display in the HTML element using v-model and any value entered in the field will be immediately placed in the linked variable. The {{variable_name}} syntax allows us to display the value of a variable in the page.

We’re just using a few of the directives Vue.js has to offer, but it gets us up and running.

<template>
    <div>
        <div class="controls-container">
            <p>Push the button to get a problem to solve</p>
            <button v-on:click="getProblem()" v-if="!activeProblem">Get Problem</button>
            <button v-on:click="checkAnswer()" v-if="activeProblem">Check Answer</button>
        </div>
        <div v-if="answerStatus === 'correct'" class="fireworks">
            Yay! You got it!
            <div class="first"></div>
            <div class="last"></div>
        </div>
        <div v-if="answerStatus === 'wrong'">
            Oops! Not quite it, try again.
        </div>
        <div v-if="activeProblem" class="problem-container">
             <div>{{problem.numberOne}}</div>
             <div>{{problem.operator}}</div>
             <div>{{problem.numberTwo}}</div>
             <div>=</div>
             <input type="number" v-model="problem.userAnswer"/>
        </div>
        <div v-if="errorMessage">
            <div class="error-text">{{errorMessage}}</div>
        </div>
    </div>
</template>

Let’s look at the script portion of our Problem component now.

We’ll focus on two main sections of our script, data and methods. We define our variables in the data section. The variables declared here will be available in the HTML part of our component.

The aptly named methods section of the script is where put the methods for our component. For the methods that make calls out to our back end APIs, we’re going to declare them as async. We’re using axios to handle our calls, and you might recognize the API end points.

<script>
    export default {
        name: 'Problem',
        props: {

        },
        data() {
            return {
                problem: {},
                activeProblem: false,
                errorMessage: null,
                answerStatus : 'unanswered'
            }
        },
        methods: {
             async getProblem() {
               const prob = await this.$axios.$get('/math/get-problem');
               this.problem = prob;
               switch(this.problem.operation) {
                     case 'ADD':
                         this.problem.operator = "+";
                         break;
                     case 'SUBTRACT':
                         this.problem.operator = "-";
                         break;
                     case 'MULTIPLY':
                         this.problem.operator = "*";
                         break;
                    case 'DIVIDE':
                         this.problem.operator = "/";
                         break;
                 }
               this.activeProblem = true;
            },
            async checkAnswer() {
                this.errorMessage = null;
                this.answerStatus = 'unanswered';

                const answer = await this.$axios.$put('/math/check-answer', this.problem);

                if (answer) {  
                     if (answer.correct) {                   
                         this.answerStatus = 'correct';
                     setTimeout(() => this.reset(), 5000);
                     } else {
                         this.answerStatus = 'wrong';
                     }
                } else {
                     this.errorMessage = "Unable to check your answer at this time";
                }
            }, reset: function(){
                this.problem = {};
                this.activeProblem =false;
                this.errorMessage = null;
                this.answerStatus = 'unanswered';
            }
        }
    };
</script>

This isn’t a tutorial on CSS and I am no CSS wizard, so I’m not going to get into the styling here. But that is the third and final portion of the Problem component. However, while we’re on the subject of CSS, I want to bring up the fireworks. Creating fireworks with CSS is beyond my current CSS skill level, so I used an example from this JS Fiddle.

Running the Application

We can start the API project using Maven:

mvn spring-boot:run

Once that’s up and running we can run our Nuxt app in development mode:

npm run dev

We can access the application in the browser at http://localhost:3000/.

Conclusion

In this post, we build a Spring Boot API for generating and verifying simple arithmetic problems and then created a Nuxt front end to access it.

The example code for the Spring Boot application is available in this BitBucket repository.

The UI is available over in this BitBucket repo.