Bootiful Development with Spring Boot and Angular

To simplify development and deployment, you want everything in the same artifact, so you put your Angular app “inside” your Spring Boot app, right? But what if you could create your Angular app as a standalone app and make cross-origin requests to your API? Hey guess what, you can do both!

I believe that most frontend developers are used to having their apps standalone and making cross-origin requests to APIs. The beauty of having a client app that can point to a server app is you can point it to any server and it makes it easy to test your current client code against other servers (e.g. test, staging, production).

This post shows how you can have the best of both worlds where the UI and the API are separate apps. You’ll learn how to create REST endpoints with Spring Data REST, configure Spring Boot to allow CORS, and create an Angular app to display its data. This app will display a list of beers from the API, then fetch a GIF from https://giphy.com/ that matches the beer’s name.

If you don’t want to code along, feel free to grab the source code from GitHub! You can also watch a video of this tutorial below.

Build an API with Spring Boot

To get started with Spring Boot, navigate to start.spring.io. In the “Search for dependencies” field, select the following:

  • DevTools: Provides auto-reloading of your application when files change
  • H2: An in-memory database
  • JPA: Standard ORM for Java
  • Rest Repositories: Allows you to expose your JPA repositories as REST endpoints
  • Web: Spring MVC with Jackson (for JSON), Hibernate Validator, and embedded Tomcat

start.spring.io

If you like the command-line better, you can use the following command to download a demo.zip file with HTTPie.

http https://start.spring.io/starter.zip bootVersion==1.5.10.RELEASE \
dependencies==devtools,h2,data-jpa,data-rest,web -d

Create a directory called spring-boot-angular-example and expand the contents of demo.zip inside it. Rename the demo directory to server and delete demo.zip.

Open the server project in your favorite IDE and run DemoApplication or start it from the command line using ./mvnw spring-boot:run.

Create a com.example.demo.beer package and a Beer.java file in it. This will be the entity that holds your data.

package com.example.demo.beer;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Beer {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    public Beer() {}

    public Beer(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Beer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Add a BeerRepository class that leverages Spring Data to do CRUD on this entity.

package com.example.demo.beer;

import org.springframework.data.jpa.repository.JpaRepository;

interface BeerRepository extends JpaRepository<Beer, Long> {
}

Add a BeerCommandLineRunner that uses this repository and creates a default set of data.

package com.example.demo.beer;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.stream.Stream;

@Component
public class BeerCommandLineRunner implements CommandLineRunner {

    private final BeerRepository repository;

    public BeerCommandLineRunner(BeerRepository repository) {
        this.repository = repository;
    }

    @Override
    public void run(String... strings) throws Exception {
        // Top beers from https://www.beeradvocate.com/lists/top/
        Stream.of("Kentucky Brunch Brand Stout", "Good Morning", "Very Hazy", "King Julius",
                "Budweiser", "Coors Light", "PBR").forEach(name ->
                repository.save(new Beer(name))
        );
        repository.findAll().forEach(System.out::println);
    }
}

Rebuild your project and you should see a list of beers printed in your terminal.

Beers printed in terminal

Add a @RepositoryRestResource annotation to BeerRepository to expose all its CRUD operations as REST endpoints.

import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource
interface BeerRepository extends JpaRepository<Beer, Long> {
}

Add a BeerController class to create an endpoint that filters out less-than-great beers.

package com.example.demo.beer;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collection;
import java.util.stream.Collectors;

@RestController
public class BeerController {
    private BeerRepository repository;

    public BeerController(BeerRepository repository) {
        this.repository = repository;
    }

    @GetMapping("/good-beers")
    public Collection<Beer> goodBeers() {

        return repository.findAll().stream()
                .filter(this::isGreat)
                .collect(Collectors.toList());
    }

    private boolean isGreat(Beer beer) {
        return !beer.getName().equals("Budweiser") &&
                !beer.getName().equals("Coors Light") &&
                !beer.getName().equals("PBR");
    }
}

Re-build your application and navigate to http://localhost:8080/good-beers. You should see the list of good beers in your browser.

Good Beers JSON

You should also see this same result in your terminal window when using HTTPie.

http localhost:8080/good-beers

Create a Project with Angular CLI

It’s cool that you created an API to display a list of beers, but APIs aren’t that cool without a UI. In this section, you’ll create a new Angular app, build services to fetch beers/images, and create components to display this data.

To create an Angular project, make sure you have Node.js and the latest Angular CLI installed.

npm install -g @angular/cli@1.6.7

From a terminal window, cd into the root of the spring-boot-angular-example directory and run the following command:

ng new client

This will create a new client directory and run npm install to install all the necessary dependencies. To verify everything works, cd into the client directory and run ng e2e. If everything works, you should see output like the following in your terminal:

[10:30:59] I/launcher - Running 1 instances of WebDriver
[10:30:59] I/direct - Using ChromeDriver directly...
Jasmine started

  client App
    ✓ should display welcome message

Executed 1 of 1 spec SUCCESS in 0.692 sec.
[10:31:01] I/launcher - 0 instance(s) of WebDriver still running
[10:31:01] I/launcher - chrome #01 passed

If you’d rather not use the command line and have IntelliJ IDEA (or WebStorm) installed, you can create a new Static Web Project and select Angular CLI.

IntelliJ new Static Web project

Create a BeerListComponent and BeerService

Thus far, you’ve created a good-beers API and an Angular app, but you haven’t created the UI to display the list of beers from your API. To do this, create a <beer-list> component by running Angular CLI’s generate component command.

$ ng generate component beer-list
  create src/app/beer-list/beer-list.component.css (0 bytes)
  create src/app/beer-list/beer-list.component.html (28 bytes)
  create src/app/beer-list/beer-list.component.spec.ts (643 bytes)
  create src/app/beer-list/beer-list.component.ts (280 bytes)
  update src/app/app.module.ts (408 bytes)

TIP: There is a g alias for generate and a c alias for component, so you can type ng g c beer-list too.

Create a beer service:

$ ng g s beer
  create src/app/beer.service.spec.ts (362 bytes)
  create src/app/beer.service.ts (110 bytes)

Create a src/app/shared/beer directory and move beer.service.* into it.

mkdir -p src/app/shared/beer
mv src/app/beer.service.* src/app/shared/beer/.

Create a src/app/shared/index.ts file and export the BeerService. The reason for this file is so you can export multiple classes and import them in one line rather than multiple.

export * from './beer/beer.service';

Modify client/src/app/shared/beer/beer.service.ts to call the “good-beers” API service.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class BeerService {

  constructor(private http: HttpClient) {}

  getAll(): Observable<any> {
    return this.http.get('http://localhost:8080/good-beers');
  }
}

Open client/src/app/app.module.ts and add HttpClientModule as an import.

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  ...
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  ...
})

Modify client/src/app/beer-list/beer-list.component.ts to use the BeerService and store the results in a local variable. Notice that you need to add the service as a provider in the @Component definition or you will see an error.

import { Component, OnInit } from '@angular/core';
import { BeerService } from '../shared';

@Component({
  selector: 'app-beer-list',
  templateUrl: './beer-list.component.html',
  styleUrls: ['./beer-list.component.css'],
  providers: [BeerService]
})
export class BeerListComponent implements OnInit {
  beers: Array<any>;

  constructor(private beerService: BeerService) { }

  ngOnInit() {
    this.beerService.getAll().subscribe(
      data => {
        this.beers = data;
      },
      error => console.log(error)
    )
  }
}

Modify client/src/app/beer-list/beer-list.component.html so it renders the list of beers.

<h2>Beer List</h2>

<div *ngFor="let b of beers">
  {{b.name}}
</div>

Update app.component.html to have the BeerListComponent below the title, removing the rest of the HTML.

<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
</div>
<app-beer-list></app-beer-list>

Make sure both apps are started (with mvn spring-boot:run in the server directory, and ng serve in the client directory) and navigate to http://localhost:4200. You should see an error in your console that you means you have to configure cross-origin resource sharing (CORS) on the server.

Failed to load http://localhost:8080/good-beers: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access.

To fix this issue, you’ll need to configure Spring Boot to allow cross-domain access from http://localhost:4200.

Configure CORS for Spring Boot

In the server project, open BeerController.java and add a @CrossOrigin annotation to enable cross-origin resource sharing (CORS) from the client (http://localhost:4200).

import org.springframework.web.bind.annotation.CrossOrigin;
...
    @GetMapping("/good-beers")
    @CrossOrigin(origins = "http://localhost:4200")
    public Collection<Beer> goodBeers() {

After making these changes, you should be able to see a list of beers from your Spring Boot API.

Beer List in Angular

To make it look a little better, add a Giphy service to fetch images based on the beer’s name. Create client/src/app/shared/giphy/giphy.service.ts and place the following code inside it.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import 'rxjs/add/operator/map';

@Injectable()
// http://tutorials.pluralsight.com/front-end-javascript/getting-started-with-angular-2-by-building-a-giphy-search-application
export class GiphyService {

  // Public beta key: https://github.com/Giphy/GiphyAPI#public-beta-key
  giphyApi = '//api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&limit=1&q=';

  constructor(public http: HttpClient) {
  }

  get(searchTerm) {
    const apiLink = this.giphyApi + searchTerm;
    return this.http.get(apiLink).map((response: any) => {
      if (response.data.length > 0) {
        return response.data[0].images.original.url;
      } else {
        return 'https://media.giphy.com/media/YaOxRsmrv9IeA/giphy.gif'; // dancing cat for 404
      }
    });
  }
}

Add an export for this class in client/src/app/shared/index.ts.

export * from './beer/beer.service';
export * from './giphy/giphy.service';

Then add it to BeerListComponent to set a giphyUrl on each beer object.

import { Component, OnInit } from '@angular/core';
import { BeerService, GiphyService } from '../shared';

@Component({
  selector: 'app-beer-list',
  templateUrl: './beer-list.component.html',
  styleUrls: ['./beer-list.component.css'],
  providers: [BeerService, GiphyService]
})
export class BeerListComponent implements OnInit {
  beers: Array<any>;

  constructor(private beerService: BeerService, private giphyService: GiphyService) { }

  ngOnInit() {
    this.beerService.getAll().subscribe(
      data => {
        this.beers = data;
        for (const beer of this.beers) {
          this.giphyService.get(beer.name).subscribe(url => beer.giphyUrl = url);
        }
      },
      error => console.log(error)
    )
  }
}

Then update beer-list.component.html to include a reference to this image.

<div *ngFor="let b of beers">
  {{b.name}}<br>
  <img width="200" src="{{b.giphyUrl}}" alt="{{b.name}}">
</div>

The result should look something like the following list of beer names with images.

Beer list with Giphy images

You’ve just created an Angular app that talks to a Spring Boot API using cross-domain requests. Congratulations!

Learn More About Spring Boot and Angular

To learn more about Angular, Spring Boot, or Okta, check out the following resources:

You can find the source code associated with this article on GitHub. If you find any bugs, please file an issue on GitHub, or ask your question on Stack Overflow with an okta tag. Of course, you can always ping me on Twitter too.

Update: To learn how to turn this application into a progressive web application that can work offline, see Build Your First Progressive Web Application with Angular and Spring Boot. I also wrote a tutorial that shows how to develop an Ionic mobile app for this Spring Boot backend.

Update 2: To see how to develop this same application with Spring Boot, React, and Okta, see Bootiful Development with Spring Boot and React.

Changelog:

  • Feb 7, 2018: Updated to use Spring Boot 1.5.10, Angular 5.2.0, and Angular CLI 1.6.7. See the code changes in the example app on GitHub. Changes to this article can be viewed in this pull request.
  • Nov 3, 2017: Updated to use Spring Boot 1.5.8, Angular 5.0.0, and Angular CLI 1.5.0. See the code changes in the example app on GitHub.

Matt Raible is a well-known figure in the Java community and has been building web applications for most of his adult life. For over 20 years, he has helped developers learn and adopt open source frameworks and use them effectively. He's a web developer, Java Champion, and Developer Advocate at Okta. Matt has been a speaker at many conferences worldwide, including Devnexus, Devoxx Belgium, Devoxx France, Jfokus, and JavaOne. He is the author of The Angular Mini-Book, The JHipster Mini-Book, Spring Live, and contributed to Pro JSP. He is a frequent contributor to open source and a member of the JHipster development team. You can find him online @mraible and raibledesigns.com.

Okta Developer Blog Comment Policy

We welcome relevant and respectful comments. Off-topic comments may be removed.