Single Page App (SPA) frameworks offer many benefits over traditional web apps which do all of their HTML generation on the server side. Popular SPA frameworks include Vue, React and Angular. Micronaut is very well suited for publishing REST APIs and is a terrific fit for implementing backend logic for SPAs.
2. A MICRONAUT WALKS INTO A SPA
ABOUT ME
▸ Zachary Klein is a Senior Software Engineer
at OCI. He has been practicing web
development since 2010 and frontend
development since 2015. He’s a
contributor to both the Grails and
Micronaut frameworks, a conference
speaker and an occasional instructor in
OCI’s training practice.
▸ Zachary’s home base is in St Louis, MO,
along with his wife, Beth, and their three
children.
3. ▸ Brief Introduction to Micronaut
▸ Restful Backends
▸ Gateways
▸ Security with JWT
▸ Building with Gradle
▸ Deploying a SPA in a JAR
A MICRONAUT WALKS INTO A SPA
AGENDA
AND GRAPHQL?
5. ▸ Designed with Microservices in mind
▸ Reactive HTTP server built on Netty
▸ AOT (Ahead of Time) Compilation for DI, AOP, and
configuration
▸ Declarative HTTP Client
▸ “Natively” Cloud-Native: service-discovery, LB,
circuit-breakers, tracing, and more
▸ Support for Java, Kotlin and Groovy
A MICRONAUT WALKS INTO A SPA
MICRONAUT
6. A MICRONAUT WALKS INTO A SPA
MICRONAUT: GETTING STARTED
~ curl -s "https://get.sdkman.io" | bash
~ source "$HOME/.sdkman/bin/sdkman-init.sh"
~ sdk install micronaut
~ mn create-app hello-world
7. A MICRONAUT WALKS INTO A SPA
MICRONAUT CLI
▸ Language defaults to Java
▸ Use --lang to specify groovy or kotlin
▸ Build tool defaults to Gradle
▸ Use --build to specify maven
▸ Run mn without arguments to enter interactive
mode - includes tab-completion
8. A MICRONAUT WALKS INTO A SPA
@Controller("/")
class HelloController {
@Get("/hello/{name}")
String hello(String name) {
return "Hello " + name;
}
}
MICRONAUT: CONTROLLERS & CLIENTS
@Client("/")
interface HelloClient {
@Get("/hello/{name}")
String hello(String name);
// Implementation generated
// at compile time
}
9. A MICRONAUT WALKS INTO A SPA
MICRONAUT: DEPENDENCY INJECTION
@Singleton //Bean definition generated at compile time
class WeatherService {
Integer currentTemp() { //... }
}
@Controller('/weather')
class WeatherController {
@Inject WeatherService weatherService
//DI computed at compile time
@Get("/")
HttpResponse<Integer> currentTemp() {
HttpResponse.ok(weatherService.currentTemp())
}
}
10. A MICRONAUT WALKS INTO A SPA
MICRONAUT: CLOUD NATIVE
//Lookup client from service-discovery registry
@Client(id="billing", path=“/billing")
interface BillingClient { ... }
//Automatically retry failing calls
@Client("https://api.external.service")
@Retryable(attempts = '3', delay = '5ms')
interface ExternalApiClient { ... }
//Immediately fail after set number of failures
//Begin accepting calls after `reset` interval
@Singleton
@CircuitBreaker(attempts = '5', reset = '300ms')
class MyService { ... }
SERVICE DISCOVERY
RETRYABLE
CIRCUIT BREAKERS
11. A MICRONAUT WALKS INTO A SPA
MICRONAUT: CLOUD NATIVE
▸ Cloud-Aware Environment Detection
▸ Cloud Provider Integration - AWS, GCP, Spring
Cloud
▸ Metrics & Monitoring
▸ Distributed Configuration
▸ Distributed Tracing
23. A MICRONAUT WALKS INTO A SPA
ENABLING CORS
▸ CORS support included in
Micronaut
▸ Disabled by default
▸ Can specify allowed
origins, methods, headers,
max age, and more.
micronaut:
application:
name: my-app
server:
cors:
enabled: true
APPLICATION.YML
24. ▸ An alternative to REST
▸ Server provides a schema that
describes exposed resources
▸ Schema defines queries and mutations
▸ Clients can request only the resources
& fields they want
▸ Client libraries can intelligently merge
queries for efficiency
A MICRONAUT WALKS INTO A SPA
GRAPHQL
https://medium.freecodecamp.org/so-whats-this-graphql-thing-i-keep-hearing-about-baf4d36c20cf?gi=e256cd305c64
26. ▸ micronaut-graphql library - contributed by Marcel Overdijk
▸ Wraps graphql-java and provides a default controller for accepting
queries & mutations
▸ The GraphQL schema still needs to be created, either manually via the
graphql-java API, or an integration library such as GORM for GraphQL
▸ Packages the GraphiQL in-browser IDE for exploring the GraphQL schema
▸ Example Projects: https://github.com/micronaut-projects/micronaut-
graphql/tree/master/examples/
A MICRONAUT WALKS INTO A SPA
MICRONAUT & GRAPHQL
dependencies {
compile “io.micronaut.graphql:micronaut-graphql”
}
BUILD.GRADLE
28. ▸ Architectural pattern for microservice-based systems
▸ Expose a single client-facing API (for SPA, mobile, etc)
▸ Minimizing integration points - decoupling
▸ https://microservices.io/patterns/apigateway.html
A MICRONAUT WALKS INTO A SPA
GATEWAYS
33. ▸ Version by URL: @Get("/v1/user/profile")
▸ Using config property:
@Value("${core.api.version}")
String version
@Get("/${version}/user/profile")
‣ Client-facing versioning can be separate from versioning
within the microservice architecture
A MICRONAUT WALKS INTO A SPA
VERSIONING AN API
core:
api:
version: v1
APPLICATION.YML
36. ▸ Partition your API
▸ Support different
client experiences/
functions (e.g, admin
vs customer)
A MICRONAUT WALKS INTO A SPA
MULTIPLE GATEWAYS
Billing
Web
MailAnalyticsInventory
Gateway
Admin Web
Admin
Gateway
Mobile
Mobile
Gateway
37. A MICRONAUT WALKS INTO A SPA
MICRONAUT PETSTORE
https://github.com/micronaut-projects/micronaut-examples/tree/master/petstore
39. A MICRONAUT WALKS INTO A SPA
JWTI: JSON WEB TOKEN
‣ Open standard for representing
claims securely between two parties
‣ Tokens can be signed with either a
secret or public/private key
‣ Standard approach for stateless
authentication
‣ Ideal for transmitting authentication
& authorization data between
microservices and single-page-apps
40. A MICRONAUT WALKS INTO A SPA
MICRONAUT SECURITY
▸ Core Micronaut Library - supports JWT, Session, Basic Auth
▸ Annotation-based API & config-based URL mappings
▸ Support for token propagation
▸ Supports RFC 6750 Bearer Token
▸ JWTs can be read from cookie
dependencies {
compile "io.micronaut:micronaut-security-jwt"
}
micronaut:
security:
enabled: true
token:
jwt:
enabled: true
signatures:
secret:
generator:
secret: changeMe
APPLICATION.YML
BUILD.GRADLE
41. A MICRONAUT WALKS INTO A SPA
@SECURED ANNOTATION
▸ @Secured annotation applied
to controllers and methods
▸ All routes blocked by default
▸ Can require authentication
and/or authorization (role-
based)
▸ Alternative: JSR-250 security
annotations are also
supported: @PermitAll,
@RolesAllowed, @DenyAll
import java.security.Principal;
@Secured("isAuthenticated()")
@Controller("/")
public class HomeController {
@Get("/")
String index(Principal principal) {
return principal.getName();
}
@Secured({"ROLE_ADMIN", "ROLE_X"})
@Get("/classified")
String classified() {
return /* REDACTED */;
}
}
42. A MICRONAUT WALKS INTO A SPA
‣ Unauthorized request is made to API
‣ Responds with 401
‣ Client POSTs to login endpoint
‣ Server responds with JWT
‣ Client includes access token in the
Authorization header for subsequent
requests
‣ Server validates the incoming token
‣ If authorized, server responds with
resource
MICRONAUT JWT SECURITY
43. A MICRONAUT WALKS INTO A SPA
‣ POST credentials to the /
login endpoint
‣ Retrieve access token
from JWT and store in
application state, a
cookie, or local storage*
JAVASCRIPT JWT SECURITY const login = (credentials, callback, errorCallback) => {
fetch(`${process.env.SERVER_URL}/login`, {
method: "POST",
headers: new Headers({
Accept: "application/json",
"Content-Type": "application/json",
}),
body: JSON.stringify(credentials)
})
.then(response => {
if (response.ok) {
return response.json();
} else {
throw Error(response.statusText);
}
})
.then(json => callback(json))
.catch(error => errorCallback(error));
};
export default { login };
AUTHCLIENT.JS
import AuthClient from “./AuthClient";
AuthClient.login(
{ username: this.username, password: this.password },
json => /* handle login success */,
error => console.error(error)
);
*Local storage is not inherently secure:
https://www.rdegges.com/2018/please-stop-using-local-storage/
44. A MICRONAUT WALKS INTO A SPA
‣ Use custom Headers
object to include access
token (if using Bearer)
JAVASCRIPT JWT SECURITY
const securedHeaders = () => {
return new Headers({
Authorization: `Bearer ${token()}`,
"Content-Type": "application/json"
});
};
const token = () => //retrieve token from store;
export default { securedHeaders };
SECURITYUTILS.JS
import { securedHeaders } from "./SecurityUtils";
fetch(`${process.env.SERVER_URL}/home`, {
method: "GET",
headers: securedHeaders()
})
.then(response => response.text())
.then(text => (this.content = text))
.catch(error => {
console.log("Connection failure: ", error);
});
45. A MICRONAUT WALKS INTO A SPA
MICRONAUT SECURITY GUIDES
https://guides.micronaut.io/tags/security.html
50. ▸ Serve the Single-Page-App from within an executable JAR
file
▸ Removes need for a separate static server
▸ SPA can be packaged with its gateway
▸ Simplifies deployment of small apps and/or IoT
deployments
A MICRONAUT WALKS INTO A SPA
DEPLOYING A SPA IN A JAR
51. ~ gradle shadowJar
BUILD SUCCESSFUL in 1s
DEPLOYMENT
A MICRONAUT WALKS INTO A SPA
JAVASCRIPT APP
~ yarn build
Creating a production build...
Compiled successfully.
53. COMBINED BUILD
A MICRONAUT WALKS INTO A SPA
~ java -jar build/libs/server-all.jar
Server Running: http://localhost:8080
54. STATIC ASSETS IN MICRONAUT
A MICRONAUT WALKS INTO A SPA
▸ Disabled by default
▸
▸
▸ List of paths, starting with classpath: or file:
▸
▸ The path from which resources should be served
micronaut.router.static-resources.*.enabled
micronaut.router.static-resources.*.paths
micronaut.router.static-resources.*.mapping
55. STATIC ASSETS IN MICRONAUT
A MICRONAUT WALKS INTO A SPA
micronaut:
router:
static-resources:
default:
enabled: true
mapping: "/**"
paths: "classpath:public"
local:
enabled: true
mapping: “/local/**"
paths: “file:path/to/files“
APPLICATION.YML
56. COMBINED BUILD
A MICRONAUT WALKS INTO A SPA
//Copy production SPA resources into server build directory
task copyClientResources(dependsOn: ':client:build') {
group = 'build'
description = 'Copy client resources into server'
}
copyClientResources.doFirst {
copy {
from project(':client').buildDir.absolutePath
into "${project(':server').buildDir}/resources/main/public"
}
}
//Build a single executable JAR with embedded SPA resources
task assembleServerAndClient(
dependsOn: ['copyClientResources', ':server:shadowJar']) {
group = 'build'
description = 'Build combined server & client JAR'
}
BUILD.GRADLE
57. COMBINED BUILD
A MICRONAUT WALKS INTO A SPA
~ java -jar build/libs/server-all.jar
Server Running: http://localhost:8080
59. A MICRONAUT WALKS INTO A SPA
MICRONAUT PETSTORE
https://github.com/micronaut-projects/micronaut-examples/tree/master/petstore
60. A MICRONAUT WALKS INTO A SPA
LEARN MORE ABOUT OCI EVENTS AND TRAINING
Events:
‣ objectcomputing.com/events
Training:
‣ objectcomputing.com/training
‣ grailstraining.com
‣ micronauttraining.com
COME SEE US
AT OUR BOOTH!
61. A MICRONAUT WALKS INTO A SPA
THANK YOU@ZacharyAKlein kleinz@objectcomputing.com