loading...
Cover image for Complete API in 5 minutes with Spring Data REST - AQAP Series

Complete API in 5 minutes with Spring Data REST - AQAP Series

brunodrugowick profile image Bruno Drugowick Updated on ・6 min read

AQAP Series (5 Part Series)

1) Complete API in 5 minutes with Spring Data REST - AQAP Series 2) Spring Boot, Vue.js, Axios and Thymeleaf with Bootstrap in 4 commits 3) Complete CRUD with Spring Boot, Vue.js, Axios 4) The most basic security for Spring Boot with Thymeleaf 5) Distributed Tracing with Spring Cloud Sleuth and Zipkin - AQAP Series

The following instructions will give you a complete REST API for two related resources in 5 minutes. You will write less than 50 lines of code.

Bootstrap the Project with Spring Initializr

Spring Initializr let's you quickly bootstrap your Spring applications by selecting dependencies. I've already prepared a configuration with the following dependencies:

  • Spring Data JPA to persist data to a database.
  • H2 Database to auto-configure an embedded relational database.
  • Rest Repositories to expose Spring Data repositories over REST.
  • Lombok to reduce boilerplate code and create in compile-time all the Java getters, setters, constructors etc.

Click here to be redirected to Spring Initializr wesite with the previous configuration loaded.

Download, unzip and load the project on your favorite IDE.

Specify the resources

There'll be a resource called User:

@Entity @Data
class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String firstName;
    private String lastName;

    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;
}

And another one called Role:

@Entity @Data
class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String name;
}

Notice the following:

  • @Entity specifies this as a JPA entity to be persisted to a database (in our case, H2).
  • @Data is a Lombok annotation that creates code in compile-time for getters, setters, constructors, toString and hash methods.
  • @Id and @GeneratedValue are JPA annotations that designate the id field as an ID for the entities and that the database IDs will be auto-generated (in this case by the database).
  • @ManyToOne and @JoinColumn relate User and Role entities.

Create and expose repositories

The Repository pattern abstracts the access to read and write data from/to a database. In this little experiment, the entities are to be persisted and the repositories implement the operations to do so.

With Spring Data, though, you won't need any implementation, since it'll be provided to us in runtime. Let's see how:

@RepositoryRestResource(collectionResourceRel = "users", itemResourceRel = "user", path = "users")
interface PersonRepository extends JpaRepository<User, Long> {}

@RepositoryRestResource(collectionResourceRel = "roles", itemResourceRel = "role", path = "roles")
interface RoleRepository extends JpaRepository<Role, Long> {}

Let's break things down:

  • @RepositoryRestResource is what exposes the entities as REST endpoints.
    • itemResourceRel specifies how to call one instance of an entity.
    • collectionResourceRel specifies how to call two or more instances of an entity.
    • path specifies the path (url) to access the resource.
  • extends JpaRepository<... is what provides all the methods to write each entity to the database. The list below was taken with the code completion feature (CTRL + Space on IntelliJ IDEA) on an instance of a repository:

Alt Text

Bootstrap some data

For demonstration purposes, let's create a import.sql file on the resources folder to populate the database with data upon app initialization.

insert into role (name) values ('USER'), ('ADMIN'), ('ROOT');
insert into user (first_name, last_name, role_id) values ('Johnny', 'Bravo', 1), ('Johnny', 'Manso', 2), ('Sidra', 'Cereser', 1), ('Chuck', 'Norris', 3);

Test some operations

Assuming you're running the application on your machine, the following snippets help you "see" the API working.

GET list of resources

On the terminal, type:

curl -i -X GET http://localhost:8080/users

You get:

HTTP/1.1 200 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/hal+json
Transfer-Encoding: chunked
Date: Tue, 21 Jan 2020 03:02:40 GMT

{
  "_embedded" : {
    "users" : [ {
      "firstName" : "Johnny",
      "lastName" : "Bravo",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/1"
        },
        "user" : {
          "href" : "http://localhost:8080/users/1"
        },
        "role" : {
          "href" : "http://localhost:8080/users/1/role"
        }
      }
    }, {
      "firstName" : "Johnny",
      "lastName" : "Manso",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/2"
        },
        "user" : {
          "href" : "http://localhost:8080/users/2"
        },
        "role" : {
          "href" : "http://localhost:8080/users/2/role"
        }
      }
    }, {
      "firstName" : "Sidra",
      "lastName" : "Cereser",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/3"
        },
        "user" : {
          "href" : "http://localhost:8080/users/3"
        },
        "role" : {
          "href" : "http://localhost:8080/users/3/role"
        }
      }
    }, {
      "firstName" : "Chuck",
      "lastName" : "Norris",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/4"
        },
        "user" : {
          "href" : "http://localhost:8080/users/4"
        },
        "role" : {
          "href" : "http://localhost:8080/users/4/role"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/users"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 4,
    "totalPages" : 1,
    "number" : 0
  }
}

On the terminal, type:

curl -i -X GET http://localhost:8080/roles

You get:

HTTP/1.1 200 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/hal+json
Transfer-Encoding: chunked
Date: Tue, 21 Jan 2020 03:10:29 GMT

{
  "_embedded" : {
    "roles" : [ {
      "name" : "USER",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/roles/1"
        },
        "role" : {
          "href" : "http://localhost:8080/roles/1"
        }
      }
    }, {
      "name" : "ADMIN",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/roles/2"
        },
        "role" : {
          "href" : "http://localhost:8080/roles/2"
        }
      }
    }, {
      "name" : "ROOT",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/roles/3"
        },
        "role" : {
          "href" : "http://localhost:8080/roles/3"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/roles{?page,size,sort}",
      "templated" : true
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/roles"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 3,
    "totalPages" : 1,
    "number" : 0
  }
}

GET one resource

On the terminal, type:

curl -i -X GET http://localhost:8080/users/1

You get:

HTTP/1.1 200 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/hal+json
Transfer-Encoding: chunked
Date: Tue, 21 Jan 2020 03:05:21 GMT

{
  "firstName" : "Johnny",
  "lastName" : "Bravo",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/1"
    },
    "user" : {
      "href" : "http://localhost:8080/users/1"
    },
    "role" : {
      "href" : "http://localhost:8080/users/1/role"
    }
  }
}

On the terminal, type:

curl -i -X GET http://localhost:8080/roles/1

You get:

HTTP/1.1 200 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/hal+json
Transfer-Encoding: chunked
Date: Tue, 21 Jan 2020 03:11:12 GMT

{
  "name" : "USER",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/roles/1"
    },
    "role" : {
      "href" : "http://localhost:8080/roles/1"
    }
  }
}

POST a new resource

On the terminal, type:

curl -i -X POST -H "Content-Type:application/json" -d '{"firstName": "Jhon", "lastName": "Benga", "role": "http://localhost:8080/roles/1"}' http://localhost:8080/users

You get:

HTTP/1.1 201 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Location: http://localhost:8080/users/5
Content-Type: application/hal+json
Transfer-Encoding: chunked
Date: Tue, 21 Jan 2020 03:09:01 GMT

{
  "firstName" : "Jhon",
  "lastName" : "Benga",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/5"
    },
    "user" : {
      "href" : "http://localhost:8080/users/5"
    },
    "role" : {
      "href" : "http://localhost:8080/users/5/role"
    }
  }
}

Ahá, did you notice the role property? Yeah, you just send the href property of the role you want to attribute to the user.

Note: this is the reason I didn't include a HAL browser on this article. I don't know how (for now) to properly configure/customize the HAL browser to post embedded entities.

Final thoughts

That was quick, wasn't it? The repository for the application is this:

Example application to serve as a boilerplate and learning purposes for Spring Data REST.

Here's all the code we wrote for the application:

@SpringBootApplication
public class SpringDataRestExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringDataRestExampleApplication.class, args);
    }
}

@Entity @Data
class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String firstName;
    private String lastName;

    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;
}

@Entity @Data
class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String name;
}

@RepositoryRestResource(collectionResourceRel = "users", itemResourceRel = "user", path = "users")
interface PersonRepository extends JpaRepository<User, Long> {}

@RepositoryRestResource(collectionResourceRel = "roles", itemResourceRel = "role", path = "roles")
interface RoleRepository extends JpaRepository<Role, Long> {}

AQAP Series

As Quickly As Possible (AQAP) is a series of quick posts on something I find interesting. I encourage (and take part on) the discussions on the comments to further explore the technology, library or code quickly explained here.

Beginners tag

I'm using this tag for the first time. Since there are rules to use it, please let me know if there's something here you (or a beginner) don't understand or that I took for granted and you're (or a beginner may be :) confused about it.


Image by Jason King por Pixabay

AQAP Series (5 Part Series)

1) Complete API in 5 minutes with Spring Data REST - AQAP Series 2) Spring Boot, Vue.js, Axios and Thymeleaf with Bootstrap in 4 commits 3) Complete CRUD with Spring Boot, Vue.js, Axios 4) The most basic security for Spring Boot with Thymeleaf 5) Distributed Tracing with Spring Cloud Sleuth and Zipkin - AQAP Series

Posted on by:

brunodrugowick profile

Bruno Drugowick

@brunodrugowick

I love helping people to understand and deal with technology. If I can build something in the process, even better!

Discussion

markdown guide
 

I think it's important to note that I like the idea of having a simple code like that persisting and providing a REST API, but I hate the idea of this architecture scaling to a bigger app without proper app layers segregating the API from the Domain of your app.

This particular post was inspired by Marcelo Bosso saying to me that Node-RED is incomparable when it comes to providing a quick API for a front-end development. =P

 

Love it.... Can you make an article of Spring Boot JWT auth using mongo dB ?

 

Hello, Sharad. On this series I'd probably do two separated articles, one about security and another a about MongoDB.

The one about security may come out soon, I'm researching about it now.

Not sure about writing about MongoDB, but if you want to try something, take a look at docs.spring.io/spring-data/mongodb....

Regards.