Simple, maintainable and customizable CRUD UIs with jOOQ and ng-admin

In this blog post I’ll show you a way to build a relatively simple CRUD UI with a Java backend and an AngularJS frontend.

The requirements for the implementation are the following:

  1. The setup should be easy.
  2. It should integrate with jOOQ
  3. It should be Maintainable (the effort of adding, changing and removing entities in the data model should be low).
  4. It should be customizable.

The toolset we will use to fulfill those requirements consists of four important libraries:

  • For a quick server setup we’ll use Spring Boot.
  • To access the database from our application, jOOQ is our friend.
  • AngularJS will be our frontend framework.
  • ng-admin will provide us with a highly configurable CRUD UI.

This blog post is backed by a demo repository where you can find the whole code used in the examples.

Application setup

The application build is configured in the build.gradle file.

For simplicity we use the embedded h2 database and we use flyway to run our database migrations.

Then we use the jOOQ code generator to generate our models and tables from our database schema.

Afterwards we build our Spring Boot application and retrieve a jar file which we can run using our jvm.

The jar is equipped with an embedded tomcat server and we use the spring-boot-starter-web artifact to build our web application.

Database model

The database model of our demo application has a person and a blog post entity.


jOOQ can easily be used with the spring-boot-starter-jooq artifact. It provides the DSLContext to the DI container.

Now we create an abstract class AbstractCRUDRepository to handle some basic database queries.

For each of our domain objects we subclass the abstract repository like this:

As you can see, we create a filtering mechanism. This will be useful for the autocompletion search in the UI.


This approach allows us to manage our entities. But what about the relationships between those entities? Especially the many-to-many relationships contain some logic we don’t want to replicate over and over again.

To face this problem, we create an abstract repository for join table records (already generated from jOOQ).

Now we can subclass this abstract repository to be able to manage one specific many to many relationships

Going REST

Now that we are equipped with a data access layer we can go and build our REST interface.

Again, we create an abstract class which keeps the common behavior of all the rest controllers.

At this point you may ask yourself: Why don’t we pull the annotations up into the abstract class to have less code replication?

I actually tried this way in a first run.

But there are two problems with this approach. You might want to have different DTOs for your REST calls. Imagine a user entity with email and password. What do you want to expose to the admin using the interface? Only the email. The other problem is that you don’t want to provide all REST methods for every resource. For example you don’t want to let an admin edit some of your application roles, but he still needs to load them when managing the access rights of a certain user.

So here is a compromise. We subclass the abstract controller again and provide the default REST endpoints. So we can decide for each entity whether we want to provide all CRUD operations. This makes sense, since we want to build a CRUD UI with low effort.

In our data model we don’t want to hide anything. So we will simply use our AbstractCRUDBackofficeController for the blog posts and persons.

We will manage the readers relation from the blog posts controller, so we have to do some extra work here:

The UI

We’re done with our backend so far. So let’s have a look at the UI. ng-admin is a highly configurable angular module. Some of it’s most important features are:

  • It talks REST
  • It ships with bootstrap, so the app is fully responsive
  • It provides configurable entity relationships

If you are curious what else ng-admin is capable of, have a look at this demo app which shows a lot of the features of ng-admin.

The integration and configuration is straight forward. Simply depend on the module and then configure your app in a configuration callback:

Your entities can be configured like this:

As you can see, we need to specify each field for our entities. For the list view as well as for the creation and edition view. The advantage of this approach is that you have full control over the entity representation in the frontend. The drawback is that you have to keep the view in sync with your database model.

There are other approaches to create CRUD UIs like Apache Isis, which implements the Naked Object pattern. This could be appealing to pure Java developers, since you annotate your domain classes. But as soon as you have to customize your CRUD UI or even have to create custom views, I personally think you are better off having full control over the frontend from the beginning.

Apart from that our CRUD application is quite resilient to changes in the data model. The Controller and repositories don’t have to be touched until we rename a table or change a relation.


To see how well we’re doing with this solution, let’s check whether we fulfill the requirements mentioned above.

  1. Easy setup:
    The setup looks not too complex to me.
  2. jOOQ integration:
    We successfully use jOOQ as the only data access library within our CRUD application.
  3. Maintainability:
    This is hard to judge. With some abstraction we built some generic controllers which can be easily extended and reused. The point that prevents me from saying that the application has an excellent maintainability is that we manually have to keep the CRUD UI fields within ng-admin in sync with the data model. But good maintainability is relative. We would have to compare this to other approaches in real-life applications.
  4. Customizability:
    Our app is highly customizable. We can create custom views within the application and provide them to angular-ui-router which is shipped with ng-admin. Also the templates can be overwritten if we need to.

It looks like a viable solution to me. If you like to discuss this solution, feel free to post into the comments below.

You may also like...

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Time limit is exhausted. Please reload the CAPTCHA.