Spring Boot App with Form Validation + Pagination + Exception Handling

In this project you’ll build a website where you can create, read, update and delete and article. You’ll use a mySQL Database with Spring Data JPA , Custom Exceptions, Form Validations with Redirect and Pagination for your Tables.

Find a working versione here.

Find the source code here.

The Frontend was created with a Material Kit provided under the MIT license on Creative Tim.

Generate your project with Spring Initializr
Necessary Dependencies: Web, JPA, mySQL, PostgreSQL (optional, only if you want to deploy on Heroku), Thymealf, DevTools (optional)

.Download and open the generated file in an IDE of your chosie (for example: IntellJ). Download the Material Kit and place it into your folder structure:

your pom.xml will look something like this

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>articles-app</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>articles-app</name>
    <description>crud application for platoiscoding</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties

##################################################################
# Heroku Properties
##################################################################
#spring.datasource.url=${JDBC_DATABSE_URL}
#spring.datasource.username=${JDBC_DATABSE_USERNAME}
#spring.datasource.password=${JDBC_DATABSE_PASSWORD}

#spring.jpa.show-sql = false
#spring.jpa.generate.ddl = true
#spring.jpa.hibernate.ddl-auto = create
#spring.datasource.initialization-mode=always

#spring.mvc.throw-exception-if-no-handler-found=true

## Thymeleaf Properties
#spring.thymeleaf.check-template-location=true
#spring.thymeleaf.cache=false

##################################################################
# Properties for Running on your IDE
##################################################################

spring.datasource.url=jdbc:mysql://localhost/articles_db?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false

spring.datasource.username=root
spring.datasource.password=password

spring.jpa.show-sql=true
## Hibernate Logging
logging.level.org.hibernate.SQL= DEBUG
#init data after every restart
spring.jpa.hibernate.ddl-auto=create
spring.datasource.initialization-mode=always

## Server Properties
server.port= 5000

## Hibernate Properties
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

## Jackson Properties
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS= false
spring.jackson.time-zone= UTC

spring.mvc.throw-exception-if-no-handler-found=true

## Thymeleaf Properties
spring.thymeleaf.check-template-location=true
spring.thymeleaf.cache=false

data.sql

INSERT INTO articles (article_id, created_at, updated_at, author, category, content, description, title) VALUES
  ( 1000, '2018-12-28 00:27:24', '2019-12-28 00:27:24', 'Wikipedia', 'Java', 'Central to the Spring Framework is its inversion of control (IoC) container, which provides a consistent means of configuring and managing Java objects using reflection. The container is responsible for managing object lifecycles of specific objects: creating these objects, calling their initialization methods, and configuring these objects by wiring them together. Objects created by the container are also called managed objects or beans. The container can be configured by loading XML (Extensible Markup Language) files or detecting specific Java annotations on configuration classes. These data sources contain the bean definitions that provide the information required to create the beans. Objects can be obtained by means of either dependency lookup or dependency injection.[12] Dependency lookup is a pattern where a caller asks the container object for an object with a specific name or of a specific type. Dependency injection is a pattern where the container passes objects by name to other objects, via either constructors, properties, or factory methods. In many cases one need not use the container when using other parts of the Spring Framework, although using it will likely make an application easier to configure and customize. The Spring container provides a consistent mechanism to configure applications and integrates with almost all Java environments, from small-scale applications to large enterprise applications. The container can be turned into a partially compliant EJB (Enterprise JavaBeans) 3.0 container by means of the Pitchfork project. Some[who?] criticize the Spring Framework for not complying with standards.[13] However, SpringSource doesn''t see EJB 3 compliance as a major goal, and claims that the Spring Framework and the container allow for more powerful programming models.[14] The programmer does not directly create an object, but describe how they should be created, by defining it in the Spring configuration file. Similarly services and components are not called directly; instead a Spring configuration file defines which services and components must be called. This IoC is intended to increase the ease of maintenance and testing.', 'The Spring Framework is an application framework and inversion of control container for the Java platform. The framework''s core features can be used by any Java application, but there are extensions for building web applications on top of the Java EE (Enterprise Edition) platform. ','Spring Framework'),
  ( 1001, '2018-12-28 00:27:24', '2019-12-28 00:27:24', 'Wikipedia', 'Java', 'While template processors are typically a separate piece of software, used as part of a system or framework, simple templating languages are commonly included in the string processing features of general-purpose programming languages, and in text processing programs, notably text editors or word processors. The templating languages are generally simple substitution-only languages, in contrast to the more sophisticated facilities in full-blown template processors, but may contain some logic. Simple examples include print format strings, found in many programming languages, and snippets, found in a number of text editors and source code editors. In word processors, templates are a common feature, while automatic filling in of the templates is often referred to as mail merge.', 'A template processor (also known as a template engine or template parser) is software designed to combine templates with a data model to produce result documents.[1][2][3] The language that the templates are written in is known as a template language or templating language. ','Template processor'),
  ( 1002, '2018-12-28 00:27:24', '2019-12-28 00:27:24', 'Wikipedia', 'Java', 'Tomcat started off as a servlet reference implementation by James Duncan Davidson, a software architect at Sun Microsystems. He later helped make the project open source and played a key role in its donation by Sun Microsystems to the Apache Software Foundation.[10] The Apache Ant software build automation tool was developed as a side-effect of the creation of Tomcat as an open source project. Davidson had initially hoped that the project would become open sourced and, since many open source projects had O''Reilly books associated with them featuring an animal on the cover, he wanted to name the project after an animal. He came up with Tomcat since he reasoned the animal represented something that could fend for itself. Although the tomcat was already in use for another O''Reilly title,[11] his wish to see an animal cover eventually came true when O''Reilly published their Tomcat book with a snow leopard on the cover in 2003.[12]', 'Apache Tomcat, often referred to as Tomcat Server, is an open-source Java Servlet Container developed by the Apache Software Foundation (ASF). Tomcat implements several Java EE specifications including Java Servlet, JavaServer Pages (JSP), Java EL, and WebSocket, and provides a "pure Java" HTTP web server environment in which Java code can run.','Apache Tomcat'),
  ( 1003, '2018-12-28 00:27:24', '2019-12-28 00:27:24', 'Wikipedia', 'Java', 'The Spring Web Flow project started as a simple extension to the Spring Web MVC framework providing web flow functionality, developed by Erwin Vervaet in 2004. In 2005 the project was introduced into the Spring portfolio by Keith Donald and grew into the official Spring sub-project it is now. The first production ready 1.0 release was made on 2006-10-26. Version 2.0, first released on 2008-04-29, saw a major internal reorganization of the framework to allow better integration with JavaServer Faces.', 'Spring Web Flow (SWF) is the sub-project of the Spring Framework that focuses on providing the infrastructure for building and running rich web applications. ','Spring Web Flow'),
  ( 1004, '2018-12-28 00:27:24', '2019-12-28 00:27:24', 'Wikipedia', 'Java', ' It is a server-based system that runs in servlet containers such as Apache Tomcat. It supports version control tools, including AccuRev, CVS, Subversion, Git, Mercurial, Perforce, TD/OMS, ClearCase and RTC, and can execute Apache Ant, Apache Maven and sbt based projects as well as arbitrary shell scripts and Windows batch commands. The creator of Jenkins is Kohsuke Kawaguchi.[4] Released under the MIT License, Jenkins is free software.[5] Builds can be triggered by various means, for example by commit in a version control system, by scheduling via a cron-like mechanism and by requesting a specific build URL. It can also be triggered after the other builds in the queue have completed. Jenkins functionality can be extended with plugins. The Jenkins project was originally named Hudson, and was renamed after a dispute with Oracle, which had forked the project and claimed rights to the project name. The Oracle fork, Hudson, continued to be developed for a time before being donated to the Eclipse Foundation. Oracle''s Hudson is no longer maintained[6][7] and was announced as obsolete in February 2017.[8]', 'Jenkins is an open source automation server written in Java. Jenkins helps to automate the non-human part of the software development process, with continuous integration and facilitating technical aspects of continuous delivery. ','Jenkins (software)'),
  ( 1005, '2018-12-28 00:27:24', '2019-12-28 00:27:24', 'Wikipedia', 'TypeScript', 'Angular 2.0 was announced at the ng-Europe conference 22-23. October 2014.[9][10] The drastic changes in the 2.0 version created considerable controversy among developers.[11] On April 30, 2015, the Angular developers announced that Angular 2 moved from Alpha to Developer Preview.[12] Angular 2 moved to Beta in December 2015,[13] and the first release candidate was published in May 2016.[14] The final version was released on September 14, 2016.Angular 6 was released on May 4, 2018.[20]. This is a major release focused less on the underlying framework, and more on the toolchain and on making it easier to move quickly with Angular in the future, like: ng update, ng add, Angular Elements, Angular Material + CDK Components, Angular Material Starter Components, CLI Workspaces, Library Support, Tree Shakable Providers, Animations Performance Improvements, and RxJS v6.', 'Angular (commonly referred to as "Angular 2+" or "Angular v2 and above")[4][5] is a TypeScript-based open-source web application framework led by the Angular Team at Google and by a community of individuals and corporations. Angular is a complete rewrite from the same team that built AngularJS.','Angular (web framework)'),
  ( 1006, '2018-12-28 00:27:24', '2019-12-28 00:27:24', 'Wikipedia', 'JavaScript', 'React can be used as a base in the development of single-page or mobile applications, as it''s optimal only for its intended use of being the quickest method to fetch rapidly changing data that needs to be recorded. However, fetching data is only the beginning of what happens on a web page, which is why complex React applications usually require the use of additional libraries for state management, routing, and interaction with an API. React was created by Jordan Walke, a software engineer at Facebook. He was influenced by XHP, an HTML component framework for PHP.[9] It was first deployed on Facebook''s newsfeed in 2011 and later on Instagram.com in 2012.[10] It was open-sourced at JSConf US in May 2013. React Native, which enables native Android, iOS, and UWP development with React, was announced at Facebook''s React.js Conf in February 2015 and open-sourced in March 2015. On April 18, 2017, Facebook announced React Fiber, a new core algorithm of React framework library for building user interfaces.[11] React Fiber was to become the foundation of any future improvements and feature development of the React framework.[12][needs update] On April 19, 2017, React 360 V1.0.0 was released to the public.[13] This allowed developers with experience using react to jump into VR development.', 'React (also known as React.js or ReactJS) is a JavaScript library[3] for building user interfaces. It is maintained by Facebook and a community of individual developers and companies.','React (JavaScript library)');

Article Entitiy

@Entity
@Table(name="articles")
public class Article extends DateAudit {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    @Column(name="article_id")
    private long articleId;

    @Size(min=2, max=100, message="The title must be between 2 and 100 characters.")
    @Column(name="title")
    private String title;

    @NotEmpty(message="Please enter a category.")
    @Column(name="category")
    private String category;

    @NotEmpty(message="Please enter the name of the author.")
    @Column(name="author")
    private String author;

    @Lob
    @Type(type = "org.hibernate.type.TextType") //heroku config
    @NotEmpty(message="The description cannot be empty.")
    @Column(name="description")
    private String description;

    @Lob
    @NotEmpty(message="The content of the article cannot be empty.")
    @Type(type = "org.hibernate.type.TextType") //heroku config
    @Column(name="content")
    private String content;

    public Article() {
        this.setCreatedAt(new Date());
        this.setUpdatedAt(new Date());
    }

    public Article(@Size(min = 2, max = 100) String title, @NotEmpty String category,
                   @NotEmpty String author, String description, @NotEmpty String content) {
        this.title = title;
        this.category = category;
        this.author = author;
        this.description = description;
        this.content = content;

        this.setCreatedAt(new Date());
        this.setUpdatedAt(new Date());
    }

  /* Getter and Setter...*/

}

ArticleRepository

@Repository
public interface ArticleRepository extends PagingAndSortingRepository<Article, Long> {
    /**
     * @return newest articleId
     */
    @Query(value = "SELECT MAX(id) FROM Article")
    Long findTopByOrderByIdDesc();

    /**
     * title+author must be unique
     * @param title     title of an article
     * @param author    author of an article
     * @return          List of articles with the same title and author
     */
    @Query("SELECT a FROM Article a WHERE a.title=:title and a.author=:author")
    List<Article> findByTitleAndAuthor(@Param("title") String title, @Param("author") String author);

    /**
     * returns all Articles from the DB as a Page Object
     * @param pageable  Abstract interface for pagination information
     * @return          a page of entities that fulfill the restrictions
     *                  specified by the Pageable object
     */
    Page<Article> findAll(Pageable pageable);
}

ArticleService (Interface)

public interface ArticleService  {

    /**
     * gets all articles from DB
     * @return      all articles as Set<Article>
     */
    Set<Article> getAllArticles();

    /**
     * saves field values in a new article object into DB
     * @param article   object containing field values
     * @return          new article from DB
     */
    Article createArticle(Article article);

    /**
     * finds an article in DB with 'articleId'
     * then updates the information with field values from 'article'
     * @param articleId     points to article object in DB
     * @param article       object containing field values
     * @return              article from DB
     */
    Article updateArticle(Long articleId, Article article);

    /**
     * deletes an article that matches the articleId
     * @param articleId     id of article
     */
    void deleteArticle(Long articleId);

    /**
     * find an article by its articleId
     * @param articleId    id of article
     * @return      article that matches the articleId
     */
    Article findById(Long articleId);

    /**
     * tests whether there is an article with te same title and author in the database
     * @param article
     * @return true if there is no article with the same author and title in the database
     */
    boolean titleAndAuthorValid(Article article);

    /**
     * returns all Articles from the DB as a Page Object
     * @param pageable  Abstract interface for pagination information
     * @return          a page of entities that fulfill the restrictions
     *                  specified by the Pageable object
     */
    Page<Article> findAll(Pageable pageable);

}

ArticleService Implementation

@Service
public class ArticleServiceImpl implements ArticleService{

    @Autowired
    private ArticleRepository articleRepository;

    @Override
    public Set<Article> getAllArticles(){
        Set<Article> articleSet =  new HashSet<>();
        articleRepository.findAll().iterator().forEachRemaining(articleSet::add);
        return articleSet;
    }

    @Override
    public Article createArticle(Article article){
        Article newArticle;
        newArticle = articleRepository.save(article);
        return newArticle;
    }

    @Override
    public boolean titleAndAuthorValid(Article article){
        Set<Article> articleSet = new HashSet<>();
        articleRepository.findByTitleAndAuthor(article.getTitle(),article.getAuthor())
                .iterator().forEachRemaining(articleSet::add);
        if (!articleSet.isEmpty()) { return false;}
        else {return true;}
    }

    @Override
    public Article updateArticle(Long articleId, Article articleDetails) {
        Article article = findById(articleId);
        article.setCategory(articleDetails.getCategory());
        article.setTitle(articleDetails.getTitle());
        article.setAuthor(articleDetails.getAuthor());
        article.setDescription(articleDetails.getDescription());
        article.setContent(articleDetails.getContent());
        //created_at is not updatable
        article.setUpdatedAt(new Date());
        articleRepository.save(article);
        return article;
    }

    @Override
    public void deleteArticle(Long articleId) {
        articleRepository.delete(findById(articleId));
    }

    @Override
    public Article findById(Long articleId){
        Optional<Article> articleOptional = articleRepository.findById(articleId);
        if (!articleOptional.isPresent()) {
            throw new ResourceNotFoundException("There is no Article with ID = " + articleId);
        }
        return articleOptional.get();
    }

    @Override
    public Page<Article> findAll(Pageable pageable) {
        return articleRepository.findAll(pageable);
    }

}

DateAudit Model for TimeStamps

package com.example.articlesapp.dateAudit;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
        value = {"createdAt", "updatedAt"},
        allowGetters = true
)
/**
 * records when the entity was created and updated
 */
public abstract class DateAudit implements Serializable {

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_at", nullable = false, updatable = false)
    @CreatedDate
    private Date createdAt;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated_at", nullable = false)
    @LastModifiedDate
    private Date updatedAt;

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }

    public Date getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Date updatedAt) {
        this.updatedAt = updatedAt;
    }
}

PagerModel for Pagination

/**
 * supporting class for pagination
 *
 * @author Michael Good
 * https://dzone.com/articles/pagingandsortingrepository-how-to-use-with-thymele
 */
public class PagerModel {

    private int buttonsToShow = 5;
    private int startPage;
    private int endPage;

    public PagerModel(int totalPages, int currentPage, int buttonsToShow) {

        setButtonsToShow(buttonsToShow);
        int halfPagesToShow = getButtonsToShow() / 2;

        if (totalPages <= getButtonsToShow()) {
            setStartPage(1);
            setEndPage(totalPages);
        } else if (currentPage - halfPagesToShow <= 0) {
            setStartPage(1);
            setEndPage(getButtonsToShow());
        } else if (currentPage + halfPagesToShow == totalPages) {
            setStartPage(currentPage - halfPagesToShow);
            setEndPage(totalPages);
        } else if (currentPage + halfPagesToShow > totalPages) {
            setStartPage(totalPages - getButtonsToShow() + 1);
            setEndPage(totalPages);
        } else {
            setStartPage(currentPage - halfPagesToShow);
            setEndPage(currentPage + halfPagesToShow);
        }
    }
    public int getButtonsToShow() {
        return buttonsToShow;
    }
    public void setButtonsToShow(int buttonsToShow) {

        if (buttonsToShow % 2 != 0) {
            this.buttonsToShow = buttonsToShow;
        } else {
            throw new IllegalArgumentException("Must be an odd value!");
        }
    }
    public int getStartPage() {
        return startPage;
    }
    public void setStartPage(int startPage) {
        this.startPage = startPage;
    }

    public int getEndPage() {
        return endPage;
    }
    public void setEndPage(int endPage) {
        this.endPage = endPage;
    }

    @Override
    public String toString() {
        return "Pager [startPage=" + startPage + ", endPage=" + endPage + "]";
    }
}

ArticleController

package com.example.articlesapp.controller;

import com.example.articlesapp.model.Article;
import com.example.articlesapp.model.PagerModel;
import com.example.articlesapp.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Optional;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;


/**
 * @author platoiscoding.com
 */
@Controller
public class ArticleController {

    /**
     * view templates
     */
    protected static final String ARTICLE_VIEW = "articles/showArticle";                    //view template for single article
    protected static final String ARTICLE_ADD_FORM_VIEW = "articles/newArticle";            //form for new article
    protected static final String ARTICLE_EDIT_FORM_VIEW = "articles/editArticle";          //form for editing an article
    protected static final String ARTICLE_PAGE_VIEW = "articles/allArticles";               //list with pagination
    protected static final String INDEX_VIEW = "index";                                     //articles with pagination

    /**
     * pagination
     */
    private static final int BUTTONS_TO_SHOW = 3;
    private static final int INITIAL_PAGE = 0;
    private static final int INITIAL_PAGE_SIZE = 5;
    private static final int[] PAGE_SIZES = { 5, 10};

    @Autowired
    private ArticleService articleService;

    /**
     * Index Page with all Articles, About and Contact
     * @param pageSize  Specifies how many articles are displayed per page
     * @param page      page number
     * @return          INDEX_VIEW
     */
    @GetMapping({"/"})
    public ModelAndView getIndex(@RequestParam("pageSize") Optional<Integer> pageSize,
                           @RequestParam("page") Optional<Integer> page) {
        ModelAndView modelAndView = initPagination(pageSize, page, INDEX_VIEW);
        return modelAndView;
    }

    /**
     * GET article by id
     * @param articleId     id of article
     * @param model         attributeValues
     * @return              view template for single article
     */
    @GetMapping("/article/{id}")
    public String getArticleById(@PathVariable(value = "id") Long articleId, Model model) {
        model.addAttribute("article", articleService.findById(articleId));
        return ARTICLE_VIEW;
    }

    /**
     * GET all articles from database
     * @param pageSize      number of articles per page
     * @param page          subset of all articles, page number
     * @return              ARTICLE_PAGE_VIEW
     */
    @GetMapping("/articles")
    public ModelAndView getAllArticles(@RequestParam("pageSize") Optional<Integer> pageSize,
                                                 @RequestParam("page") Optional<Integer> page) {
        ModelAndView modelAndView = initPagination(pageSize, page, ARTICLE_PAGE_VIEW);
        return modelAndView;
    }

    /**
     * FORM for NEW article
     * @param model     attributesValues
     * @return          ARTICLE_ADD_FORM_VIEW
     */
    @GetMapping("/article/new")
    public String newArticle(Model model){

        //in case of redirection model will contain article
        if (!model.containsAttribute("article")) {
            model.addAttribute("article", new Article());
        }
        return ARTICLE_ADD_FORM_VIEW;
    }

    /**
     * CREATE_NEW_ARTICLE checks...
     *          (1)field values for errors
     *          (2)whether databse already contains an article with the same name and author as field values
     *
     * @param article       entity
     * @param result        result of validation of field values from ARTICLE_ADD_FORM_VIEW
     * @param model         attributeValues
     * @param attr          stores flash attributes; used when method returns a redirect view name
     * @return  if !valid: redirect: '/article/new'
     *          else:      redirect: '/article/{articleId}'
     */
    @PostMapping("/article/create")
    public String createArticle(@Valid Article article, BindingResult result, Model model,
                                RedirectAttributes attr) {

        if (result.hasErrors() || articleService.titleAndAuthorValid(article) == false) {

            //After the redirect: flash attributes pass attributes to the model
            attr.addFlashAttribute("org.springframework.validation.BindingResult.article", result);
            attr.addFlashAttribute("article", article);
            return "redirect:/article/new";
        }
        Article newArticle = articleService.createArticle(article);
        model.addAttribute("article", newArticle);

        return "redirect:/article/" + newArticle.getArticleId();
    }

    /**
     * FORM for EDIT article
     * @param articleId
     * @param model     attributeValues
     * @return          ARTICLE_EDIT_FORM_VIEW
     */
    @GetMapping("/article/{id}/edit")
    public String editArticle(@PathVariable(value = "id") Long articleId, Model model) {
        /*
            in case of redirection from '/article/{id}/update'
            model will contain article with field values
         */
        if (!model.containsAttribute("article")) {
            model.addAttribute("article", articleService.findById(articleId));
        }
        return ARTICLE_EDIT_FORM_VIEW;
    }

    /**
     * UPDATE article with field values from ARTICLE_EDIT_FORM_VIEW
     *
     * @param articleId
     * @param articleDetails    entity
     * @param result            result of validation of field values from ARTICLE_ADD_FORM_VIEW
     * @param model             attributeValues
     * @param attr              stores flash attributes; used when method returns a redirect view name
     * @return  if !valid: redirect: '/article/{articleId}/edit'
     *          else:      redirect: '/article/{articleId}'
     */
    @RequestMapping(path = "/article/{id}/update", method = RequestMethod.POST)
    public String updateArticle(@PathVariable(value = "id") Long articleId, @Valid Article articleDetails,
                                BindingResult result, Model model, RedirectAttributes attr) {

        if (result.hasErrors() || articleService.titleAndAuthorValid(articleDetails) == false) {

            ///After the redirect: flash attributes pass attributes to the model
            attr.addFlashAttribute("org.springframework.validation.BindingResult.article", result);
            attr.addFlashAttribute("article", articleDetails);
            return "redirect:/article/" + articleDetails.getArticleId() + "/edit";
        }

        articleService.updateArticle(articleId, articleDetails);
        model.addAttribute("article", articleService.findById(articleId));
        return "redirect:/article/" + articleId;
    }


    /**
     * DELETE article by id from database
     * @param articleId     id of article
     * @return              redirect: '/articles'
     */
    @RequestMapping(value= "/article/{id}/delete")
    public String deleteArticle(@PathVariable("id") Long articleId) {
        Article article = articleService.findById(articleId);
        String title = article.getTitle();
        articleService.deleteArticle(articleId);
        return "redirect:/articles";
    }

    /**
     * Initializes pagination
     * @param pageSize      number of articles per page
     * @param page          subset of all articles, page number
     * @return              Model and View with Articles and Pagination
     */
    public ModelAndView initPagination(Optional<Integer> pageSize,Optional<Integer> page, String url){
        ModelAndView initModelView = new ModelAndView(url);
        // If pageSize == null, return initial page size
        int evalPageSize = pageSize.orElse(INITIAL_PAGE_SIZE);
        /*
            If page == null || page < 0 (to prevent exception), return initial size
            Else, return value of param. decreased by 1
        */
        int evalPage = (page.orElse(0) < 1) ? INITIAL_PAGE : page.get() - 1;

        Page<Article> articlesList = articleService.findAll(PageRequest.of(evalPage, evalPageSize));
        PagerModel pager = new PagerModel(articlesList.getTotalPages(),articlesList.getNumber(),BUTTONS_TO_SHOW);

        initModelView.addObject("articlesList",articlesList);
        initModelView.addObject("selectedPageSize", evalPageSize);
        initModelView.addObject("pageSizes", PAGE_SIZES);
        initModelView.addObject("pager", pager);
        return initModelView;
    }
}

ExceptionController

@ControllerAdvice
public class ExceptionController {

    /**
     * when no article found for an articleId
     * @param exception     404 Error, Ressource Not Found
     * @return              error template
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(ResourceNotFoundException.class)
    public ModelAndView ResourceNotFoundView(ResourceNotFoundException exception){
        ModelAndView modelAndView = new ModelAndView("errorView");
        modelAndView.addObject("title", "Resource Not Found");
        modelAndView.addObject("ex", exception);
        return modelAndView;
    }

    /**
     * wrong url format
     * @param exception     400 Error, Bad Request
     * @return              error template
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BadRequestException.class)
    public ModelAndView BadRequestView(BadRequestException exception){
        ModelAndView modelAndView = new ModelAndView("errorView");
        modelAndView.addObject("title", "Bad Request");
        modelAndView.addObject("ex", exception);
        return modelAndView;
    }

    /**
     * catches other exceptions
     * @param exception     NumberFormat, No Handler Found, Illegal Argument
     * @return              error template
     */
    @ExceptionHandler({NumberFormatException.class, NoHandlerFoundException.class, IllegalArgumentException.class})
    public ModelAndView NumberFormatView(Exception exception){
        ModelAndView modelAndView = new ModelAndView("errorView");
        modelAndView.addObject("title", "Oops...");
        modelAndView.addObject("ex", exception);
        return modelAndView;
    }
}

BadRequestException

package com.example.articlesapp.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {

    public BadRequestException() {
        super();
    }

    public BadRequestException(String message) {
        super(message);
    }

    public BadRequestException(String message, Throwable cause) {
        super(message, cause);
    }
}

ResourceNotFoundException

package com.example.articlesapp.exception;


import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {

    public ResourceNotFoundException() {
        super();
    }

    public ResourceNotFoundException(String message) {
        super(message);
    }

    public ResourceNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

This concludes the Backend. For The Frontend consists of a fragments folder (for navigation, header, footer snippets), an exception view template and the CRUD views for the articles.

fragments/footer.html

<footer class="footer" data-background-color="black">
    <div class="container">
        <nav class="float-left">
        </nav>
        <div class="copyright float-right">
            &copy;
            <script>
                document.write(new Date().getFullYear())
            </script>, made with <i class="material-icons">favorite</i> by
            <a th:href="@{https://www.creative-tim.com}" target="_blank">Creative Tim</a> for a better web.
        </div>
    </div>
</footer>

fragments/head.html

<head>
    <meta charset="utf-8" />
    <link rel="apple-touch-icon" sizes="76x76" th:href="@{/img/apple-icon.png}">
    <link rel="icon" type="image/png" th:href="@{/img/favicon.png}">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <title>
        ArticlesApp by platoiscoding.com & Material Kit by Creative Tim
    </title>
    <meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no' name='viewport' />
    <!--     Fonts and icons     -->
    <link rel="stylesheet" type="text/css" th:href="@{https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Slab:400,700|Material+Icons}" />
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css">
    <!-- CSS Files -->

    <link th:href="@{/css/material-kit.css?v=2.0.5}" rel="stylesheet" />
    <!-- CSS Just for demo purpose, don't include it in your project -->
    <link th:href="@{/demo/demo.css}" rel="stylesheet" />

    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
</head>

fragments/header.html

<div class="page-header header-filter clear-filter purple-filter" data-parallax="true" style="background-image: url('./img/pexels/background3.jpg');">
    <div class="container">
        <div class="row">
            <div class="col-md-8 ml-auto mr-auto">
                <div class="brand">
                    <h1>Articles App</h1>
                    <h4>Serves as an example for a CRUD Table, Custom Exception Handlers, Pagination and Field Validation with Error Messages and Redirect.</h4>
                    <h5>Programmed by platoiscoding.com, find source on <a class="h4" th:href="@{'https://github.com/platoiscodingcom/articles_app'}">GitHub</a>
                    FrontEnd Design by Creative Tim. Provided under MIT License. Find the Source <a class="h4" th:href="@{'https://www.creative-tim.com/product/material-kit'}">Here</a>. Background Image from pexels.com</h5>
                </div>
            </div>
        </div>
    </div>
</div>

fragments/navbarPrimary.html 

<nav class="navbar navbar-expand-lg bg-primary">
    <div class="container">
        <div class="navbar-translate">
            <a class="navbar-brand" href="/presentation.html?135b31&135b31"></a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" aria-expanded="false" aria-label="Toggle navigation">
                <span class="sr-only">Toggle navigation</span>
                <span class="navbar-toggler-icon"></span>
                <span class="navbar-toggler-icon"></span>
            </button>
        </div>

    </div>
</nav>

fragments/navbarTransparent.html

<nav class="navbar navbar-transparent navbar-color-on-scroll fixed-top navbar-expand-lg" color-on-scroll="100" id="sectionsNav">
    <div class="container">
        <div class="navbar-translate">
            <a class="navbar-brand" th:href="@{/}"> Articles App </a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" aria-expanded="false" aria-label="Toggle navigation">
                <span class="sr-only">Toggle navigation</span>
                <span class="navbar-toggler-icon"></span>
                <span class="navbar-toggler-icon"></span>
                <span class="navbar-toggler-icon"></span>
            </button>
        </div>
        <div class="collapse navbar-collapse">
            <ul class="navbar-nav ml-auto">
                <li class="dropdown nav-item">
                    <a th:href="@{/article/new}" class="nav-link">
                        <i class="fas fa-plus"></i> New Article
                    </a>
                </li>
                <li class="dropdown nav-item">
                    <a th:href="@{/articles}" class="nav-link">
                        <i class="material-icons">apps</i> All Articles
                    </a>
                </li>
            </ul>
        </div>
    </div>
</nav>

fragments/scripts.html

<!--   Core JS Files   -->
<script th:src="@{/js/core/jquery.min.js}" type="text/javascript"></script>
<script th:src="@{/js/core/popper.min.js}" type="text/javascript"></script>
<script th:src="@{/js/core/bootstrap-material-design.min.js}" type="text/javascript"></script>
<!-- Control Center for Material Kit: parallax effects, scripts for the example pages etc -->
<script th:src="@{/js/material-kit.js?v=2.0.5}" type="text/javascript"></script>

articles/allArticles.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:include="fragments/head"></th:block>

<body class="index-page sidebar-collapse">
<th:block th:include="fragments/navbarTransparent"></th:block>

<div class="main">
    <th:block th:include="fragments/navbarPrimary"></th:block>
    <div class="section">
        <div class="container">
            <div class="row">
                <div class="col">

                    <div class="title">
                        <h2>Table with Pagination</h2>
                    </div>

                    <table class="table table-responsive highlight">
                    <tr>
                        <th>Title</th>
                        <th>Author</th>
                        <th>Category</th>
                        <th>Updated At</th>
                        <th></th>
                        <th></th>
                        <th></th>
                    </tr>
                    </thead>
                    <tbody>
                    <tr th:each = "article : ${articlesList}">
                        <td th:text="${article.title}"></td>
                        <td th:text="${article.author}"></td>
                        <td th:text="${article.category}"></td>
                        <td th:text="${#dates.format(article.updatedAt, 'dd-MMM-yyyy')}"></td>
                        <td><a th:href="@{/article/{id}(id=${article.articleId})}"><i class="fab fa-readme"></i></a></td>
                        <td><a th:href="@{/article/{id}/edit(id=${article.articleId})}"><i class="far fa-edit"></i></a></td>
                        <td><a th:href="@{/article/{id}/delete(id=${article.articleId})}"><i class="fas fa-trash-alt"></i></a></td>
                    </tr>
                    </tbody>
                </table>
                </div>
            </div>
        </div>
        <br>
        &nbsp;&nbsp;&nbsp;
        <br>
        <div class="container">
            <div class="row">
                <div class="col">
                    <!--include if you want to select pagesize: 5,10 -->
                    <!--
                    <div>
                        <select id="pageSizeSelect">
                            <option th:each="pageSize : ${pageSizes}" th:text="${pageSize}"
                                    th:value="${pageSize}"
                                    th:selected="${pageSize} == ${selectedPageSize}"></option>
                        </select>
                    </div>-->

                    <div th:if="${articlesList.totalPages != 1}" class="section">
                        <ul class="pagination pagination-primary">
                            <li class="page-item" th:class="${articlesList.number == 0} ? disabled">
                                <a class="page-link" th:href="@{/articles/(pageSize=${selectedPageSize}, page=1)}">«</a>
                            </li>
                            <li class="page-item" th:class="${articlesList.number == 0} ? disabled">
                                <a class="page-link" th:href="@{/articles/(pageSize=${selectedPageSize}, page=${articlesList.number})}">←</a>
                            </li>
                            <li th:class="${articlesList.number == (page - 1)} ? 'active page-item'"
                                th:each="page : ${#numbers.sequence(pager.startPage, pager.endPage)}">
                                <a class="page-link" th:href="@{/articles/(pageSize=${selectedPageSize}, page=${page})}" th:text="${page}"></a>
                            </li>
                            <li class="page-item" th:class="${articlesList.number + 1 == articlesList.totalPages} ? disabled">
                                <a class="page-link" th:href="@{/articles/(pageSize=${selectedPageSize}, page=${articlesList.number + 2})}">→</a>
                            </li>
                            <li class="page-item" th:class="${articlesList.number + 1 == articlesList.totalPages} ? disabled">
                                <a class="page-link" th:href="@{/articles/(pageSize=${selectedPageSize}, page=${articlesList.totalPages})}">»</a>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<th:block th:include="fragments/footer"></th:block>

<th:block th:include="fragments/scripts"></th:block>
</body>

</html>

articles/editArticle.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:include="fragments/head"></th:block>

<body class="index-page sidebar-collapse">
<th:block th:include="fragments/navbarTransparent"></th:block>

<div class="main">
    <th:block th:include="fragments/navbarPrimary"></th:block>
    <div class="section">
        <div class="container">
            <div class="row">
                <div class="col-md-8 offset-md-2 col-sm-12">
                    <div class="title">
                        <h2>Edit Article</h2>
                    </div>
                    <form th:object="${article}" th:action="@{/article/{id}/update(id=${article.articleId})}" th:method="post">

                        <div class="form-group invisible">
                            <input th:field="*{articleId}" id="articleId" type="text" class="form-control validate">
                            <label for="author">articleId</label>
                        </div>

                        <div class="form-group">
                            <input th:field="*{author}" id="author" type="text" class="form-control validate">
                            <label for="author">Author</label>
                            <span class="text-danger errorMsg" th:if="${#fields.hasErrors('author')}" th:errors="*{author}"></span>
                        </div>

                        <div class="form-group">
                            <input th:field="*{title}" id="title" type="text" class="form-control validate">
                            <label for="author">Title</label>
                            <span class="text-danger errorMsg" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></span>
                        </div>

                        <div class="form-group">
                            <input th:field="*{category}" id="category" type="text" class="form-control validate">
                            <label for="category">Category</label>
                            <span class="text-danger errorMsg" th:if="${#fields.hasErrors('category')}" th:errors="*{category}"></span>
                        </div>

                        <div class="form-group">
                            <textarea th:field="*{description}" id="description" class="form-control" rows="6"></textarea>
                            <label for="description">Description</label>
                            <span class="text-danger errorMsg" th:if="${#fields.hasErrors('description')}" th:errors="*{description}"></span>
                        </div>

                        <div class="form-group">
                            <textarea th:field="*{content}" id="content" class="form-control" rows="16"></textarea>
                            <label for="content"><h6>Content</h6></label>
                            <span class="text-danger errorMsg" th:if="${#fields.hasErrors('content')}" th:errors="*{content}"></span>
                        </div>

                        <button type="submit" class="btn btn-dark">Save</button>
                        <a class="btn btn-primary" th:href="@{'/articles'}">Cancel</a>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
<th:block th:include="fragments/footer"></th:block>
<th:block th:include="fragments/scripts"></th:block>
</body>

</html>

articles/newArticle.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:include="fragments/head"></th:block>

<body class="index-page sidebar-collapse">
<th:block th:include="fragments/navbarTransparent"></th:block>

<div class="main">
    <th:block th:include="fragments/navbarPrimary"></th:block>
    <div class="section">
        <div class="container">
            <div class="row">
                <div class="col-md-8 offset-md-2 col-sm-12">
                    <div class="title">
                        <h2>New Article</h2>
                    </div>

                    <form th:object="${article}" th:action="@{/article/create}" th:method="post">

                        <div class="form-group">
                        <input th:field="*{author}" id="author" type="text" class="form-control validate">
                        <label for="author">Author</label>
                        <span class="text-danger errorMsg" th:if="${#fields.hasErrors('author')}" th:errors="*{author}"></span>
                    </div>

                        <div class="form-group">
                        <input th:field="*{title}" id="title" type="text" class="form-control validate">
                        <label for="author">Title</label>
                        <span class="text-danger errorMsg" th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></span>
                    </div>

                        <div class="form-group">
                        <input th:field="*{category}" id="category" type="text" class="form-control validate">
                        <label for="category">Category</label>
                        <span class="text-danger errorMsg" th:if="${#fields.hasErrors('category')}" th:errors="*{category}"></span>
                    </div>

                        <div class="form-group">
                        <textarea th:field="*{description}" id="description" class="form-control" rows="6"></textarea>
                        <label for="description">Description</label>
                        <span class="text-danger errorMsg" th:if="${#fields.hasErrors('description')}" th:errors="*{description}"></span>
                    </div>

                        <div class="form-group">
                        <textarea th:field="*{content}" id="content" class="form-control" rows="16"></textarea>
                        <label for="content"><h6>Content</h6></label>
                        <span class="text-danger errorMsg" th:if="${#fields.hasErrors('content')}" th:errors="*{content}"></span>
                    </div>

                        <button type="submit" class="btn btn-dark">Save</button>
                        <a class="btn btn-primary" th:href="@{'/articles'}">Cancel</a>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
<th:block th:include="fragments/footer"></th:block>
<th:block th:include="fragments/scripts"></th:block>
</body>

</html>

articles/showArticle.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:include="fragments/head"></th:block>

<body class="index-page sidebar-collapse">
<th:block th:include="fragments/navbarTransparent"></th:block>

<div class="main">
    <th:block th:include="fragments/navbarPrimary"></th:block>
    <div class="section">
        <div class="container">
            <div class="row">
                <div class="col-md-8 offset-md-2 col-sm-12">
                    <div class="title">
                        <h2 th:text="${article.title}"></h2>
                    </div>
                    <h6 th:text="${article.category}">Category</h6>
                    <h6 th:text="'written by ' + ${article.author}">Author</h6>
                    <h6 th:text="'created at ' + ${#dates.format(article.createdAt, 'dd-MMM-yyyy HH:mm')}"></h6>
                    <h6 th:text="'updated at ' + ${#dates.format(article.updatedAt, 'dd-MMM-yyyy HH:mm')}"></h6>
                    <p th:text="${article.description}"></p>
                    <p th:text="${article.content}"></p>
                </div>
            </div>
            &nbsp;
            &nbsp;
            <div class="row">
                <div class="col-md-8 offset-md-2 col-sm-12">
                    <a th:href="@{'/article/' + ${article.articleId} + '/edit'}"
                       class="btn btn-primary">Edit</a>
                </div>
            </div>
        </div>
    </div>
</div>
<th:block th:include="fragments/footer"></th:block>
<th:block th:include="fragments/scripts"></th:block>
</body>

</html>

errorView.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:include="fragments/head"></th:block>

<body class="index-page sidebar-collapse">
<th:block th:include="fragments/navbarTransparent"></th:block>
<th:block th:include="fragments/header"></th:block>
<div class="main main-raised">
    <div class="section section-basic">
        <div class="container">
            <div class="row">
                <div class="col-md-8 offset-md-2 col-sm-12">

                    <div class="title">
                        <h2 th:text="${title}"></h2>
                    </div>
                    <h6 th:text="${ex.message}"></h6>
                </div>
            </div>
        </div>
    </div>
</div>

<th:block th:include="fragments/footer"></th:block>

<th:block th:include="fragments/scripts"></th:block>
</body>

</html>

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<th:block th:include="fragments/head"></th:block>

<body class="index-page sidebar-collapse">
    <th:block th:include="fragments/navbarTransparent"></th:block>
    <th:block th:include="fragments/header"></th:block>
    <div class="main main-raised">
        <div class="section section-basic">
            <div class="container">
                <div class="row">
                    <div class="col-md-8">
                        <div class="container">
                            <div class="row" th:each = "article : ${articlesList}">
                                <div class="col">
                                    <div class="title">
                                        <h2 th:text="${article.title}"></h2>
                                    </div>
                                    <div>
                                        <h6 th:text="'written by ' + ${article.author} + ' | ' + ${article.category}
                                               + ' | ' + ${#dates.format(article.updatedAt, 'dd-MM-yyyy HH:mm')}"></h6>
                                        <p th:text="${article.description}"></p>
                                    </div>
                                    <div class="container">
                                        <div class="row">
                                            <div class="col-sx-6 offset-sx-3"></div>
                                            <a th:href="@{'/article/' + ${article.articleId}}"
                                             class="btn btn-primary">continue</a>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <div class="container">
                            <div class="row">
                                <div class="col">
                                    <div th:if="${articlesList.totalPages != 1}" class="section">
                                        <ul class="pagination pagination-primary">
                                            <li class="page-item" th:class="${articlesList.number == 0} ? disabled">
                                                <a class="page-link" th:href="@{/(pageSize=${selectedPageSize}, page=1)}">«</a>
                                            </li>
                                            <li class="page-item" th:class="${articlesList.number == 0} ? disabled">
                                                <a class="page-link" th:href="@{/(pageSize=${selectedPageSize}, page=${articlesList.number})}">←</a>
                                            </li>
                                            <li th:class="${articlesList.number == (page - 1)} ? 'active page-item'"
                                                th:each="page : ${#numbers.sequence(pager.startPage, pager.endPage)}">
                                                <a class="page-link" th:href="@{/(pageSize=${selectedPageSize}, page=${page})}" th:text="${page}"></a>
                                            </li>
                                            <li class="page-item" th:class="${articlesList.number + 1 == articlesList.totalPages} ? disabled">
                                                <a class="page-link" th:href="@{/(pageSize=${selectedPageSize}, page=${articlesList.number + 2})}">→</a>
                                            </li>
                                            <li class="page-item" th:class="${articlesList.number + 1 == articlesList.totalPages} ? disabled">
                                                <a class="page-link" th:href="@{/(pageSize=${selectedPageSize}, page=${articlesList.totalPages})}">»</a>
                                            </li>
                                        </ul>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="container">
                            <div class="row">
                                <div class="col-sm-12">
                                    <div class="title">
                                        <h2>About</h2>
                                    </div>
                                    <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
                                        tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
                                        At vero eos et a et ea rebum. Stet clita kasd
                                        gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. </p>
                                    <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
                                        tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
                                        At vero eos et accusam et justo duo dolores et ea rebum. .</p>
                                </div>
                            </div>
                            <div class="row">
                                <div class="col-sm-12">
                                    <div class="title">
                                        <h2>Contact</h2>
                                    </div>
                                    <p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
                                        tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
                                        At vero eos et accusam et justo duo dolores et ea rebum. .</p>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

  <th:block th:include="fragments/footer"></th:block>

  <th:block th:include="fragments/scripts"></th:block>
</body>

</html>

This concludes the Frontend. Thanks for Reading.

Run the App and open it in your browser under localhost:5000

Leave a Reply