As now all major application server vendors are Jakarta EE 8 certified, we are ready to start a new era of enterprise Java. Most of the examples on the internet lack a full-stack approach and just focus on the backend. With this post, I want to share a simple full-stack example following best practices with Jakarta EE, MicroProfile, React, and PostgreSQL. This includes a Flyway setup to migrate the database schema and TypeScript for the frontend application. At the end of this blog post, you'll be able to connect your React application to a Jakarta EE & MicroProfile backend to display data from PostgreSQL.
Setup the backend for Jakarta EE and MicroProfile
The backend uses Java 11 and Maven and to build the project. Next to the Jakarta EE and MicroProfile dependencies, I'm adding the PostgreSQL driver and Flyway for the schema migration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <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> <groupId>de.rieckpil.blog</groupId> <artifactId>guide-to-jakarta-ee-with-react-and-postgresql</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <!-- configure the version numbers --> </properties> <dependencies> <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-api</artifactId> <version>${jakarta.jakartaee-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.eclipse.microprofile</groupId> <artifactId>microprofile</artifactId> <version>${microprofile.version}</version> <type>pom</type> <scope>provided</scope> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>${postgresql.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> <version>${flyway-core.version}</version> </dependency> </dependencies> <build> <finalName>guide-to-jakarta-ee-with-react-and-postgresql</finalName> </build> </project> |
The backend exposes one REST endpoint to retrieve all available books inside the database. To show you the MicroProfile integration inside a Jakarta EE application, I'm injecting a config property to limit the number of books:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @Path("books") public class BookResource { @Inject @ConfigProperty(name = "book_list_size", defaultValue = "10") private Integer bookListSize; @PersistenceContext private EntityManager entityManager; @GET @Produces(MediaType.APPLICATION_JSON) public Response getAllBooks() { List<Book> allBooks = this.entityManager .createQuery("SELECT b FROM Book b", Book.class) .setMaxResults(bookListSize) .getResultList(); return Response.ok(allBooks).build(); } } |
The JPA Book
entity looks like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Entity public class Book { @Id @GeneratedValue private Long id; private String title; private String author; private String excerpt; private String isbn; private String genre; private LocalDateTime published; // ... getters & setters } |
Furthermore, as the frontend application will, later on, run on a different port, we have to add a CORS filter for the JAX-RS resource:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Provider public class CorsFilter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { responseContext.getHeaders() .add("Access-Control-Allow-Origin", "*"); responseContext.getHeaders() .add("Access-Control-Allow-Credentials", "true"); responseContext.getHeaders() .add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization"); responseContext.getHeaders() .add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD"); } } |
Prepare the PostgreSQL database schema with Flyway
Flyway is a Java library to version your database schema and evolve it over time. For the schema migration I'm using a singleton EJB with the @Startup
annotation to make sure the schema is updated only once:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Startup @Singleton @TransactionManagement(TransactionManagementType.BEAN) public class FlywayUpdater { @Resource(lookup = "jdbc/postgresql") private DataSource dataSource; @PostConstruct public void initFlyway() { System.out.println("Starting to migrate the database schema with Flyway"); Flyway flyway = Flyway.configure().dataSource(dataSource).load(); flyway.migrate(); System.out.println("Successfully applied latest schema changes"); } } |
It's important to set the transaction management for this class to TransactionManagementType.Bean
. This will delegate the transaction handling to the bean and not the container. Besides the normal Jakarta EE transaction management, where the application server takes care of committing and rollback, Flyway will do it itself.
With this setup, Flyway will evaluate the schema version on every application startup. New schema changes are then applied to PostgreSQL if required. There are further ways to migrate the database schema with Flyway using a CLI or Maven Plugin.
For this simple example, I'm using two database scripts: one to create the table for the Book
entity :
1 2 3 4 5 6 7 8 9 | CREATE TABLE book ( id BIGINT PRIMARY KEY, title VARCHAR(255) NOT NULL, excerpt TEXT, author VARCHAR(255) NOT NULL, isbn VARCHAR (20) NOT NULL, genre VARCHAR(255), published TIMESTAMP ); |
… and another script to populate some books:
1 2 3 4 | INSERT INTO book VALUES (1, 'Jakarta EE 8', 'All you need to know about Jakarta EE 8', 'Duke', '...', 'Java', '...'); INSERT INTO book VALUES (2, 'React 16', 'Effective Frontend Development with React', 'Duke', '...', 'React', '...'); INSERT INTO book VALUES (3, 'MicroProfile 3', 'All you need to know about Jakarta EE 8', 'Duke', '...', 'Java', '...'); INSERT INTO book VALUES (4, 'Jakarta EE 9', 'All you need to know about Jakarta EE 8', 'Duke', '...', 'Java', null); |
Important note: Please make sure to add an empty flyway.location
file to the folder of your migrations script (in this example /src/main/resources/db/migration
) to use Flyway with Open Liberty.
Prepare Open Liberty for PostgreSQL
The Open Liberty application server recently announced (since 19.0.0.7) its first-class support for PostgreSQL. With this, the configuration of the JDBC data source requires a little bit less XML code.
We still have to provide the JDBC driver for PostgreSQL and link to it in the server.xml
file. As I'm using Docker for this example, I'm extending the default Open Liberty image to add the custom server.xml
and the JDBC driver. You can find the latest JDBC driver for PostgreSQL on Maven Central.
1 2 3 4 | FROM open-liberty:kernel-java11 COPY --chown=1001:0 postgresql-42.2.8.jar /opt/ol/wlp/lib/ COPY --chown=1001:0 target/guide-to-jakarta-ee-with-react-and-postgresql.war /config/dropins/ COPY --chown=1001:0 server.xml /config |
Next, we configure Open Liberty for MicroProfile 3.0 & Jakarta EE 8 (the javaee-8.0
feature works for Jakarta EE as well) and our PostgreSQL data source :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?xml version="1.0" encoding="UTF-8"?> <server description="new server"> <featureManager> <feature>javaee-8.0</feature> <feature>microProfile-3.0</feature> </featureManager> <httpEndpoint id="defaultHttpEndpoint" httpPort="9080" httpsPort="9443"/> <quickStartSecurity userName="duke" userPassword="dukeduke"/> <dataSource id="DefaultDataSource" jndiName="jdbc/postgresql"> <jdbcDriver libraryRef="postgresql-library"/> <properties.postgresql serverName="book-store" portNumber="5432" databaseName="postgres" user="postgres" password="postgres"/> </dataSource> <library id="postgresql-library"> <fileset dir="/opt/ol/wlp/lib"/> </library> </server> |
Make sure to add the DefaultDataSource
as an id for the dataSource
configuration, so JPA will take it without any further configuration.
PS: If you are looking for a reference guide to set up the JDBC data source on a different application server, have a look at this cheat sheet.
Create the React application with TypeScript
For this example, React and TypeScript is used to create the frontend application. To bootstrap a new React application with TypeScript you can use create-react-app
as the following:
1 | npx create-react-app my-app --typescript |
This creates a new React project with everything you need to start. In addition, I've added semantic-ui-react to get some pre-built components:
1 2 | npm install semantic-ui-react npm install semantic-ui-css |
The TypeScript types are already included in the packages above and you don't have to install anything else.
Next, we can start by creating the React components for this application. To reduce complexity, I'll just use the already provided App
component and a BookTable
component. The App
is going to fetch the data from our Jakarta EE backend and pass it to the BookTable
component to render the data inside a table.
Both components are functional components and the data is fetched with a React Effect Hook and the Fetch browser API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | const App: React.FC = () => { const [data, setData] = useState<Array<Book> | Error>(); useEffect(() => { fetch('http://localhost:9080/resources/books') .then(response => response.json() as Promise<Book[]>) .then(data => setData(data)) .catch(error => setData(new Error(error.statusText))) }, []); let content; if (!data) { content = <Message>Loading</Message>; } else if (data instanceof Error) { content = <Message negative>An error occurred while fetching the data</Message>; } else { content = <BookTable books={data}></BookTable>; } return ( <Container> <Header as='h2'>Available Books</Header> {content} </Container> ); } export default App; |
Inside the BookTable
we can use the Table component from semantic-ui-react to render the result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | export const BookTable: React.FC<BookTableProps> = ({books}) => { return ( <Table celled> <Table.Header> <Table.Row> <Table.HeaderCell>ID</Table.HeaderCell> <Table.HeaderCell>Title</Table.HeaderCell> <Table.HeaderCell>Genre</Table.HeaderCell> <Table.HeaderCell>Excerpt</Table.HeaderCell> <Table.HeaderCell>ISBN</Table.HeaderCell> <Table.HeaderCell>Published</Table.HeaderCell> </Table.Row> </Table.Header> <Table.Body> {books.map(book => <Table.Row key={book.id}> <Table.Cell>{book.id}</Table.Cell> <Table.Cell>{book.title}</Table.Cell> <Table.Cell>{book.genre}</Table.Cell> <Table.Cell>{book.excerpt}</Table.Cell> <Table.Cell>{book.isbn}</Table.Cell> <Table.Cell>{book.published}</Table.Cell> </Table.Row> )} </Table.Body> </Table> ); }; |
For the frontend deployment, I'm using an nginx Docker image and copy the static files to it:
1 2 | FROM nginx:1.17.4 COPY build /usr/share/nginx/html |
The final result of Jakarta EE, MicroProfile, React, and PostgreSQL
Once everything is up and running, the frontend should look like the following:
You can find the whole source code on GitHub and instructions to deploy the example on your local machine.
If you are looking for further quickstart examples like this, take a look at the Start Here overview.
Have fun creating Jakarta EE & MicroProfile applications with React and PostgreSQL,
Phil