Hosting a single page application with spring boot - spring

So I am trying to host a Single Page Application alongside a normal REST API with spring.
What this means is that all requests that goes to the normal /api/ endpoints should be handled by the respective controller and all other requests should be directed to the resources in the folder /static/built
I have gotten this to work by catching all NoHandlerFoundExceptions and redirecting to either the js file or the html file. And then used a WebMvcConfigurer to map the static content.
But this all seems like a hack to me, so is there a less hacky way of doing it?

Managed to have React+ReactRouter app working by adding following mapping:
#Controller
public class RedirectController {
#GetMapping(value = {"/{regex:\\w+}", "/**/{regex:\\w+}"})
public String forward404() {
return "forward:/";
}
}
This was inspired by https://stackoverflow.com/a/42998817/991894

The easiest way I get my SPAs to work with a Spring backend API is to have 2 different controllers: one for the root index page of the SPA and the other controller is used to manage various RESTful API endpoints:
Here are my two controllers:
MainController.java
#Controller
public class MainController {
#RequestMapping(value = "/")
public String index() {
return "index";
}
}
MonitoringController.java
#RestController
#RequestMapping(value = "api")
public class MonitoringEndpoints {
#GetMapping(path = "/health", produces = "application/hal+json")
public ResponseEntity<?> checkHealth() throws Exception {
HealthBean healthBean = new HealthBean();
healthBean.setStatus("UP");
return ResponseEntity.ok(healthBean);
}
}
Notice how the API endpoint controller utilizes the '#RestConroller' annotation while the main controller utilizes the '#Conroller' annotation. This is because of how Thymeleaf utilizes it's ViewResolver. See:
Spring Boot MVC, not returning my view
Now go ahead and place your index.html page at src/main/resources/templates/index.html because Spring by default looks for your html pages within this location.
My index.html pages looks like this:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<meta charset="UTF-8"/>
<title>EDP Monitoring Tool</title>
</head>
<body>
<!-- Entry point to our ReactJS single page web application -->
<div id="root"></div>
<script type="text/javascript" src="built/bundle.js"></script>
</body>
</html>
Not sure how you're wiring up your frontend, whether that is a ReactJS app or something but I believe this information would be helpful for you. Let me know if I can answer additional questions for you.
In addition, if you're using Webpack, you can set up the entry point of your JS files via the webpack.config.js file under the entry key so like so:
entry: ['./src/main/js/index.js']

I think you're looking for the term URL Rewrite.
E.g. https://getpostcookie.com/blog/url-rewriting-for-beginners/

Related

How to reference/build the URL of a (named) Spring Web #RequestMapping on a HTML page using Thymeleaf?

I just started using Spring Web & Thymeleaf for the development of a simple webapplication.
It is going pretty well but so far I am unable to figure out how to prevent duplicate code for the hrefs and various paths/routes.
Example Spring get mapping:
...
#GetMapping(path="/hello")
public String hello(){
return "hello";
}
...
First HTML page (index.html) containing href to '/hello', using Thymeleaf:
...
<a th:href="#{/hello}">Hello</a>
...
Second HTML page (home.html) containing href to '/hello', using Thymeleaf:
...
<a th:href="#{/hello}">Hello</a>
...
How can I arrange my code so that I don't have to manual update the href on each HTML page when I change the path of a #GetMapping?
Basically I would like to assign the path '/hello' to a variable which i can then reference at each href and getmapping
BR, Kazi
I have been googling for a couple of hours but am unable to find any usable information on my problem.
I have noticed the 'name' attribute of the Spring '#GetMapping' annotation but have no idea if this provides a solution or how it should work together with Thymeleaf.
Also i know that the Django framework for Python based webapplications provides the ability to name routes (using urlpatterns) and reference these from within its templating language. I would expect a similar solution is available for the Spring framework.
I manage to find two solutions:
Solution 1
Using the capital letters of the controller name in combination with the name of the requestmethod, as described in section 11.1 'Building URIs to controllers' of the official Thymeleaf document Thymeleaf Tutorial: Thymeleaf + Spring. The capital letters of the controller name and the requestmethod name are separate by a '#'.
Important: The 'name' attribute of the #RequestMapping must not be used else Thymeleaf won't be able to resolve the view!
Note: Possibly this solution is preferred in case path variables or request parameters must be added to the url. (I have not investigated this.)
Example Controller
public class ExampleController {
#RequestMapping("/data")
public String getData(Model model) {
...
return "template"
}
#RequestMapping("/data")
public String getDataParam(#RequestParam String type) {
...
return "template"
}
}
Example links
<a th:href="${(#mvc.url('EC#getData')).build()}">Get Data Param</a>
<a th:href="${(#mvc.url('EC#getDataParam').arg(0,'internal')).build()}">Get
Data Param</a>
Solution 2
Using the 'name' attribute of the #RequestMapping. As discribe in the following answer to Stackoverflow question Url building by #RequestMapping name not working as expected with custom names
The first example only uses the 'name' attribute in the #RequestMapping at the method level.
Example Controller 1
#Controller
public class ExampleController {
#RequestMapping(value="/", method=RequestMethod.GET, name="home")
public String index(Model model) {
return "index"
}
}
Example link 1
<a th:href="${(#mvc.url('home')).build()}">Home</a>
The second example uses the 'name' attribute in the #RequestMapping at the method level and type level.
Example Controller 2
#Controller
#RequestMapping(value="/information", name="info")
public class ExampleController {
#RequestMapping(value="/organisation", method=RequestMethod.GET,
name="org")
public String contact(Model model) {
return "organisation"
}
}
Example link 2
<a th:href="${(#mvc.url('info#org')).build()}">Contact</a>

Two end points work without configuration - showing same view

I am using Spring Boot (2.5.6)'s Controller like this:
#Controller
public class WebController {
#GetMapping("/index")
public String indexPage () {
return "index";
}
}
And when I hit these two URLs:
http://localhost:8080/index
http://localhost:8080
Same Thymeleaf view index.html is served. To my understanding on hitting http://localhost:8080 I should get - Whitelabel Error Page.
In the past I have used something like this #RequestMapping(value = { "/", "/index" }, method = RequestMethod.GET) extensively. What I am missing/overlooking here?
Spring Boot, through auto-configuration, will add a WelcomePageHandlerMapping. The WelcomePageHandlerMapping will mimic the behavior of the welcome-page support in Servlet containers like Tomcat.
By default it will try to locate an index.html in either the static or template directory and, if needed, use the available templating framework to render this page.

Need equivalent of JSP tag in Thymeleaf

I am new to Thymeleaf. In jsp world I could change the theme of my Springboot Web Application using the following tags in "head" of a jsp:
<spring:theme code="stylesheet" var="themeName" />
<link href='<spring:url value="css/${themeName}"/>' rel="stylesheet" />
What should I write "as the exact equivalent of above" if I am going to use "Thymeleaf template" instead of jsp? I am using (and must use) spring boot (with web and data). Can someone please point me a way out?
Update
Adding more information.
I have my config and beans as per following:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/static/css**", "/resources/static/**", "/static/")
.setCacheControl(CacheControl.maxAge(2, TimeUnit.HOURS).cachePublic());
}
#Bean
public ThemeSource themeSource() {
ResourceBundleThemeSource themeSource = new ResourceBundleThemeSource();
themeSource.setBasenamePrefix("theme/");
return themeSource;
}
#Bean
public ThemeResolver themeResolver() {
CookieThemeResolver resolver = new CookieThemeResolver();
resolver.setDefaultThemeName("cosmo");
return resolver;
}
And also I have bunch of (e.g. Cosmo.BootStrap.min.css) under themes folder. When I select the theme name from JSP, the theme of my entire webapp gets changed.
In short, using Thymeleaf, in my "header.html fragment", I am trying to acheive something similar to this (${somename}) -
<link href="css/${somename}.css" rel="stylesheet" type="text/css" />
For now I am clueless. Please give some guidance.
You can always build dynamic URLs in Thymeleaf like:
<head th:with="themeName=${'stylesheet'}">
<link th:href="#{|/css/${themeName}|}" rel="stylesheet"/>
</head>
Now themeName can either be populated from the controller in the Model or ModelMap class, or computed in the template HTML.

Spring - store object in session without MVC

I have a clean javascript + html + css frontend project, which sends a POST request to a backend endpoint.
Backend is a SpringBoot application.
I need to store a shopping cart into server's session and not into browser's session. Previously the project was a Spring MVC + Thymeleaf app, but I am rewriting it to JS + Spring.
In previous app version is was handled by #ModelAttribute and #SessionAttribute annotations, but simple "copy-paste" of all classes and configs does not solve the issue.
Example Controller:
#Controller
#SessionAttributes("basket")
public class LabelDesignController implements IPageModel {
#ModelAttribute("basket")
public Basket populateBasketForm() {
logger.info("[Model Attribute] populating [Basket] form.");
return new Basket();
}
#PostMapping(value = "/myEndPoint", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Object myEndPoint(#ModelAttribute("basket") Basket b, Item newItem) {
// some logic here
}
}
Item is a POJO class.
EDIT:
forgot to mention, the frontend are static HTML pages with vanilla JavaScript (even no jQuery). they are running on Nginx server. The backend is running on mvn spring-boot:run command.

Playframework2 like reverse routing in spring

Can Anyone can advice me routing mechanism in spring.
I use thymeleaf for my view and I would like to use class names and method names for my url in views- just like in playframework.
But I like in spring that I define url before the controller method declaration.
Whaitting for Your sugestion. Thanks.
Since version 4.1, Spring Framework provides a way to generate routes to resources from templates (i.e. reverse routing in views).
You can check the reference documentation on the subject, but it's basically using auto-generated named routes for that.
I don't know if Thymeleaf supports this in its standard dialect, but you could quite easily extend it; if not, this is probably a feature that could be contributed to the Thymeleaf project.
Let's say you have a MyUserController like this:
#Controller
public class MyResourceController {
#RequestMapping("/user/{name}")
public String showUser(String name, Model model) {
...
return "show";
}
}
With such a dialect, you could then refer to an action like this:
<a th:uri="mvcUrl('MRC#ShowUser').buildAndExpand('bob')">Show user Bob</a>
<!-- will generate "/user/bob" -->
This is the general flow in spring framework.
Whenever user makes a request, it will first go to Spring's DispatcherServlet. The DispatcherServlet job is to send the request to spring mvc controller (custom controller)
You can define your custom controller like this:
Controller: (code snippet)
package nl.springexamples.mvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
#Controller
public class HomeController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test(){
return "test";
}
}
In servlet- context file , mention the directory/package path of your controller.
Example: <context:component-scan base-package="nl.springexamples.mvc"/>
In the above controller, it is returning string 'test' which is name of the view file(usually, it will be jsp).
JSP File: test.jsp
<%# page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1> Welcome to Spring!!!</h1>
</body>
</html>
Define this logical mapping of string name to view file in servlet-context like this:
Example: How to define internalViewResolver is as shown below
<!-- Resolves views selected for rendering by #Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value ="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
I think, that's pretty much about spring mvc and it's routing flow. I hope it helped you.

Resources