Some Rest with Vert.x

This post is part of the In­tro­duc­tion to Vert.x se­ries. So, let’s have a quick look about the con­tent of the pre­vi­ous posts. In the first post, we de­vel­oped a very sim­ple Vert.x 3 ap­pli­ca­tion, and saw how this ap­pli­ca­tion can be tested, pack­aged and ex­e­cuted. In the last post, we saw how this ap­pli­ca­tion be­came con­fig­urable and how we can use a ran­dom port in test.

Well, noth­ing fancy… Let’s go a bit fur­ther this time and de­velop a CRUD-​ish ap­pli­ca­tion. So an ap­pli­ca­tion ex­pos­ing an HTML page in­ter­act­ing with the back­end using a REST API. The level of REST­full­ness of the API is not the topic of this post, I let you de­cide as it’s a very slip­pery topic.

So, in other words we are going to see:

  • Vert.x Web - a frame­work that let you cre­ate Web ap­pli­ca­tions eas­ily using Vert.x
  • How to ex­pose sta­tic re­sources
  • How to de­velop a REST API

The code de­vel­oped in this post is avail­able on the post-3 branch of this Github project. We are going to start from the post-2 code­base.

So, let’s start.

Vert.x Web

As you may have no­tices in the pre­vi­ous posts, deal­ing with com­plex HTTP ap­pli­ca­tion using only Vert.x Core would be kind of cum­ber­some. That’s the main rea­son be­hind Vert.x Web. It makes the de­vel­op­ment of Vert.x base web ap­pli­ca­tions re­ally easy, with­out chang­ing the phi­los­o­phy.

To use Vert.x Web, you need to up­date the pom.xml file to add the fol­low­ing de­pen­dency:

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web</artifactId>
  <version>3.0.0</version>
</dependency>

That’s the only thing you need to use Vert.x Web. Sweet, no ?

Let’s now use it. Re­mem­ber, in the pre­vi­ous post, when we re­quested http://lo­cal­host:8080, we reply a nice Hello World mes­sage. Let’s do the same with Vert.x Web. Open the io.vertx.blog.first.MyFirstVerticle class and change the start method to be:

@Override
public void start(Future<Void> fut) {
 // Create a router object.
 Router router = Router.router(vertx);

 // Bind "/" to our hello message - so we are still compatible.
 router.route("/").handler(routingContext -> {
   HttpServerResponse response = routingContext.response();
   response
       .putHeader("content-type", "text/html")
       .end("<h1>Hello from my first Vert.x 3 application</h1>");
 });

 // Create the HTTP server and pass the "accept" method to the request handler.
 vertx
     .createHttpServer()
     .requestHandler(router::accept)
     .listen(
         // Retrieve the port from the configuration,
         // default to 8080.
         config().getInteger("http.port", 8080),
         result -> {
           if (result.succeeded()) {
             fut.complete();
           } else {
             fut.fail(result.cause());
           }
         }
     );
}

You may be sur­prise by the length of this snip­pet (in com­par­i­son to the pre­vi­ous code). But as we are going to see, it will make our app on steroids, just be pa­tient.

As you can see, we start by cre­at­ing a Router ob­ject. The router is the cor­ner­stone of Vert.x Web. This ob­ject is re­spon­si­ble for dis­patch­ing the HTTP re­quests to the right han­dler. Two other con­cepts are very im­por­tant in Vert.x Web:

  • Routes - which let you de­fine how re­quest are dis­patched
  • Han­dlers - which are the ac­tual ac­tion pro­cess­ing the re­quests and writ­ing the re­sult. Han­dlers can be chained.

If you un­der­stand these 3 con­cepts, you have un­der­stood every­thing in Vert.x Web.

Let’s focus on this code first:

router.route("/").handler(routingContext -> {
  HttpServerResponse response = routingContext.response();
  response
      .putHeader("content-type", "text/html")
      .end("<h1>Hello from my first Vert.x 3 application</h1>");
});

It routes re­quests ar­riv­ing on ”/” to the given han­dler. Han­dlers re­ceive a RoutingContext ob­ject. This han­dler is quite sim­i­lar to the code we had be­fore, and it’s quite nor­mal as it ma­nip­u­lates the same type of ob­ject: HttpServerResponse.

Let’s now have a look to the rest of the code:

vertx
    .createHttpServer()
    .requestHandler(router::accept)
    .listen(
        // Retrieve the port from the configuration,
        // default to 8080.
        config().getInteger("http.port", 8080),
        result -> {
          if (result.succeeded()) {
            fut.complete();
          } else {
            fut.fail(result.cause());
          }
        }
    );
}

It’s ba­si­cally the same code as be­fore, ex­cept that we change the re­quest han­dler. We pass router::accept to the han­dler. You may not be fa­mil­iar with this no­ta­tion. It’s a ref­er­ence to a method (here the method accept from the router ob­ject). In other worlds, it in­structs vert.x to call the accept method of the router when it re­ceives a re­quest.

Let’s try to see if this work:

mvn clean package
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar

By open­ing http://localhost:8080 in your browser you should see the Hello mes­sage. As we didn’t change the be­hav­ior of the ap­pli­ca­tion, our tests are still valid.

Exposing static resources

Ok, so we have a first ap­pli­ca­tion using vert.x web. Let’s see some of the ben­e­fits. Let’s start with serv­ing sta­tic re­sources, such as an index.html page. Be­fore we go fur­ther, I should start with a dis­claimer: “the HTML page we are going to see here is ugly like hell : I’m not a UI guy”. I should also add that there are prob­a­bly plenty of bet­ter ways to im­ple­ment this and a myr­iad of frame­works I should try, but that’s not the point. I tried to keep things sim­ple and just re­ly­ing on JQuery and Twit­ter Boot­strap, so if you know a bit of JavaScript you can un­der­stand and edit the page.

Let’s cre­ate the HTML page that will be the entry point of our ap­pli­ca­tion. Cre­ate an index.html page in src/main/resources/assets with the con­tent from here. As it’s just a HTML page with a bit of JavaScript, we won’t de­tail the file here. If you have ques­tions, just post com­ments.

Ba­si­cally, the page is a sim­ple CRUD UI to man­age my col­lec­tion of not-​yet-finished bot­tles of Whisky. It was made in a generic way, so you can trans­pose it to your own col­lec­tion. The list of prod­uct is dis­played in the main table. You can cre­ate a new prod­uct, edit one or delete one. These ac­tions are re­ly­ing on a REST API (that we are going to im­ple­ment) through AJAX calls. That’s all.

Once this page is cre­ated, edit the io.vertx.blog.first.MyFirstVerticle class and change the start method to be:

@Override
public void start(Future<Void> fut) {
 Router router = Router.router(vertx);
 router.route("/").handler(routingContext -> {
   HttpServerResponse response = routingContext.response();
   response
       .putHeader("content-type", "text/html")
       .end("<h1>Hello from my first Vert.x 3 application</h1>");
 });

 // Serve static resources from the /assets directory
 router.route("/assets/*").handler(StaticHandler.create("assets"));

 vertx
     .createHttpServer()
     .requestHandler(router::accept)
     .listen(
         // Retrieve the port from the configuration,
         // default to 8080.
         config().getInteger("http.port", 8080),
         result -> {
           if (result.succeeded()) {
             fut.complete();
           } else {
             fut.fail(result.cause());
           }
         }
     );
}

The only dif­fer­ence with the pre­vi­ous code is the router.route("/assets/*").handler(StaticHandler.create("assets")); line. So, what does this line mean? It’s ac­tu­ally quite sim­ple. It routes re­quests on “/as­sets/*” to re­sources stored in the “as­sets” di­rec­tory. So our index.html page is going to be served using http://localhost:8080/assets/index.html.

Be­fore test­ing this, let’s take a few sec­onds on the han­dler cre­ation. All pro­cess­ing ac­tions in Vert.x web are im­ple­mented as han­dler. To cre­ate a han­dler you al­ways call the create method.

So, I’m sure you are im­pa­tient to see our beau­ti­ful HTML page. Let’s build and run the ap­pli­ca­tion:

mvn clean package
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar

Now, open your browser to http://localhost:8080/assets/index.html. Here it is… Ugly right? I told you.

As you may no­tice too… the table is empty, this is be­cause we didn’t im­ple­ment the REST API yet. Let’s do that now.

REST API with Vert.x Web

Vert.x Web makes the im­ple­men­ta­tion of REST API re­ally easy, as it ba­si­cally routes your URL to the right han­dler. The API is very sim­ple, and will be struc­tured as fol­lows:

  • GET /api/whiskies => get all bot­tles (getAll)
  • GET /api/whiskies/:id => get the bot­tle with the cor­re­spond­ing id (getOne)
  • POST /api/whiskies => add a new bot­tle (addOne)
  • PUT /api/whiskies/:id => up­date a bot­tle (updateOne)
  • DELETE /api/whiskies/id => delete a bot­tle (deleteOne)

We need some data…

But be­fore going fur­ther, let’s cre­ate our data ob­ject. Cre­ate the src/main/java/io/vertx/blog/first/Whisky.java with the fol­low­ing con­tent:

package io.vertx.blog.first;

import java.util.concurrent.atomic.AtomicInteger;

public class Whisky {

  private static final AtomicInteger COUNTER = new AtomicInteger();

  private final int id;

  private String name;

  private String origin;

  public Whisky(String name, String origin) {
    this.id = COUNTER.getAndIncrement();
    this.name = name;
    this.origin = origin;
  }

  public Whisky() {
    this.id = COUNTER.getAndIncrement();
  }

  public String getName() {
    return name;
  }

  public String getOrigin() {
    return origin;
  }

  public int getId() {
    return id;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setOrigin(String origin) {
    this.origin = origin;
  }
}

It’s a very sim­ple bean class (so with get­ters and set­ters). We choose this for­mat be­cause Vert.x is re­ly­ing on Jack­son to han­dle the JSON for­mat. Jack­son au­to­mates the se­ri­al­iza­tion and de­se­ri­al­iza­tion of bean classes, mak­ing our code much sim­pler.

Now, let’s cre­ate a cou­ple of bot­tles. In the MyFirstVerticle class, add the fol­low­ing code:

// Store our product
private Map<Integer, Whisky> products = new LinkedHashMap<>();
// Create some product
private void createSomeData() {
  Whisky bowmore = new Whisky("Bowmore 15 Years Laimrig", "Scotland, Islay");
  products.put(bowmore.getId(), bowmore);
  Whisky talisker = new Whisky("Talisker 57° North", "Scotland, Island");
  products.put(talisker.getId(), talisker);
}

Then, in the start method, call the createSomeData method:

@Override
public void start(Future<Void> fut) {

  createSomeData();

  // Create a router object.
  Router router = Router.router(vertx);

  // Rest of the method
}

As you have no­ticed, we don’t re­ally have a back­end here, it’s just a (in-​memory) map. Adding a back­end will be cov­ered by an­other post.

Get our products

Enough dec­o­ra­tion, let’s im­ple­ment the REST API. We are going to start with GET /api/whiskies. It re­turns the list of bot­tles in a JSON Array.

In the start method, add this line just below the sta­tic han­dler line:

router.get("/api/whiskies").handler(this::getAll);

This line in­structs the router to han­dle the GET re­quests on “/api/whiskies” by call­ing the getAll method. We could have in­lined the han­dler code, but for clar­ity rea­sons let’s cre­ate an­other method:

private void getAll(RoutingContext routingContext) {
  routingContext.response()
      .putHeader("content-type", "application/json; charset=utf-8")
      .end(Json.encodePrettily(products.values()));
}

As every han­dler our method re­ceives a RoutingContext. It pop­u­lates the response by set­ting the content-type and the ac­tual con­tent. Be­cause our con­tent may con­tain weird char­ac­ters, we force the charset to UTF-8. To cre­ate the ac­tual con­tent, no need to com­pute the JSON string our­self. Vert.x lets us use the Json API. So Json.encodePrettily(products.values()) com­putes the JSON string rep­re­sent­ing the set of bot­tles.

We could have used Json.encodePrettily(products), but to make the JavaScript code sim­pler, we just re­turn the set of bot­tles and not an ob­ject con­tain­ing ID => Bottle en­tries.

With this in place, we should be able to re­trieve the set of bot­tle from our HTML page. Let’s try it:

mvn clean package
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar

Then open the HTML page in your browser (http://localhost:8080/assets/index.html), and should should see:

I’m sure you are cu­ri­ous, and want to ac­tu­ally see what is re­turned by our REST API. Let’s open a browser to http://localhost:8080/api/whiskies. You should get:

[ {
  "id" : 0,
  "name" : "Bowmore 15 Years Laimrig",
  "origin" : "Scotland, Islay"
}, {
  "id" : 1,
  "name" : "Talisker 57° North",
  "origin" : "Scotland, Island"
} ]

Create a product

Now we can re­trieve the set of bot­tles, let’s cre­ate a new one. Un­like the pre­vi­ous REST API end­point, this one need to read the re­quest’s body. For per­for­mance rea­son, it should be ex­plic­itly en­abled. Don’t be scared… it’s just a han­dler.

In the start method, add these lines just below the line end­ing by getAll:

router.route("/api/whiskies*").handler(BodyHandler.create());
router.post("/api/whiskies").handler(this::addOne);

The first line en­ables the read­ing of the re­quest body for all routes under “/api/whiskies”. We could have en­abled it glob­ally with router.route().handler(BodyHandler.create()).

The sec­ond line maps POST re­quests on /api/whiskies to the addOne method. Let’s cre­ate this method:

private void addOne(RoutingContext routingContext) {
  final Whisky whisky = Json.decodeValue(routingContext.getBodyAsString(),
      Whisky.class);
  products.put(whisky.getId(), whisky);
  routingContext.response()
      .setStatusCode(201)
      .putHeader("content-type", "application/json; charset=utf-8")
      .end(Json.encodePrettily(whisky));
}

The method starts by re­triev­ing the Whisky ob­ject from the re­quest body. It just reads the body into a String and passes it to the Json.decodeValue method. Once cre­ated it adds it to the back­end map and re­turns the cre­ated bot­tle as JSON.

Let’s try this. Re­build and restart the ap­pli­ca­tion with:

mvn clean package
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar

Then, re­fresh the HTML page and click on the Add a new bottle but­ton. Enter the data such as: “Jame­son” as name and “Ire­land” as ori­gin (purists would have no­ticed that this is ac­tu­ally a Whiskey and not a Whisky). The bot­tle should be added to the table.

Status 201?

As you can see, we have set the re­sponse sta­tus to 201. It means CREATED, and is the gen­er­ally used in REST API that cre­ate an en­tity. By de­fault vert.x web is set­ting the sta­tus to 200 mean­ing OK.

Finishing a bottle

Well, bot­tles do not last for­ever, so we should be able to delete a bot­tle. In the start method, add this line:

router.delete("/api/whiskies/:id").handler(this::deleteOne);

In the URL, we de­fine a path pa­ra­me­ter :id. So, when han­dling a match­ing re­quest, Vert.x ex­tracts the path seg­ment cor­re­spond­ing to the pa­ra­me­ter and let us ac­cess it in the han­dler method. For in­stance, /api/whiskies/0 maps id to 0.

Let’s see how the pa­ra­me­ter can be used in the han­dler method. Cre­ate the deleteOne method as fol­lows:

private void deleteOne(RoutingContext routingContext) {
  String id = routingContext.request().getParam("id");
  if (id == null) {
    routingContext.response().setStatusCode(400).end();
  } else {
    Integer idAsInteger = Integer.valueOf(id);
    products.remove(idAsInteger);
  }
  routingContext.response().setStatusCode(204).end();
}

The path pa­ra­me­ter is re­trieved using routingContext.request().getParam("id"). It checks whether it’s null (not set), and in this case re­turns a Bad Request re­sponse (sta­tus code 400). Oth­er­wise, it re­moves it from the back­end map.

Status 204?

As you can see, we have set the re­sponse sta­tus to 204 - NO CONTENT. Re­sponse to the HTTP Verb delete have gen­er­ally no con­tent.

The other methods

We won’t de­tail getOne and updateOne as the im­ple­men­ta­tions are straight­for­ward and very sim­i­lar. Their im­ple­men­ta­tions are avail­able on GitHub.

Cheers!

It’s time to con­clude this post. We have seen how Vert.x Web lets you im­ple­ment a REST API eas­ily and how it can serve sta­tic re­sources. A bit more fancy than be­fore, but still pretty easy.

In the next post, we are going to im­prove our tests to cover the REST API.

Say Tuned & Happy Cod­ing !

Next post

Unit and Integration Tests

Let’s refresh our mind about what we developed so far in the introduction to vert.x series. We forgot an important task. We didn’t test the API.

Read more
Previous post

Vert.x Application Configuration

In our previous post, we developed a very simple Vert.x 3 application, and saw how this application can be tested, packaged and executed. That was nice, wasn’t it?

Read more
Related posts

Unit and Integration Tests

Let’s refresh our mind about what we developed so far in the introduction to vert.x series. We forgot an important task. We didn’t test the API.

Read more

My first Vert.x 3 Application

Let's say, you heard someone saying that Vert.x is awesome. Ok great, but you may want to try it by yourself. Well, the next natural question is “where do I start ?”

Read more

Using the asynchronous SQL client

Finally, back... This post is the fifth post of the introduction to vert.x blog series, after a not-that-small break. In this post we are going to see how we can use JDBC in a vert.x application.

Read more