Rest controllers

The examples so far always relied on modifications of the index page of the Song controller. Although it is easy for displaying tutorial examples this approach is highly inappropriate for real-world scenarios. Luckily, Glagol DSL provides a built-in Rest API support in controllers.

Index action

Every rest controller supports the standard Rest actions which are based on the well-known HTTP 1.1 request methods. As you might have noticed already, so far we used the index action for testing. Generally speaking, when we want to run the index we simply make a GET request to the route that the controller defines. The response can be any value. Usually, the Rest index will return a list of resources. In our case, this fits with the result of querying for all entities. Therefore, lets modify our Songs index to select all song entities:

namespace MusicLib

rest controller /song {

    repository<Song> songs = get selfie;

    index {
        return songs.findAll();
    }
}

We already had this exact bit in the previous tutorial. Lets keep it this way!

Show action

What if we want to request a single resource? For this task Glagol DSL provides the developers with a show action. Lets introduce it:

namespace MusicLib

rest controller /song {

    repository<Song> songs = get selfie;

    index {
        return songs.findAll();
    }

    show (int id) {
        return songs.find(id);
    }
}

The main difference is that the show action accepts an argument which is used as a criteria for finding a resource. In the example above we rely on the existing find repository method that will do the job.

Now compile the sources and call the following curl command:

$ curl localhost:8081/song/1

Naturally, the show action is initiated by a GET request method in combination with an argument appended to the end of the route. Therefore, a show request URI has the /song/{parameter} format.

However, there is an alternative way of finding entities when we rely on the primary key field that is annotated with @id. For the purpose, we can even avoid our manually created find method or the repository altogether. To put it simply, developers can utilize the @autofind annotation to the first parameter of the action like this:

namespace MusicLib

rest controller /song {

    repository<Song> songs = get selfie;

    index {
        return songs.findAll();
    }

    show (@autofind Song song) {
        return song;
    }
}

Lets look at the differences. Instead of accepting int id as a parameter, we type Song song preceded by the @autofind annotation. This very annotation tells Glagol DSL to attempt to find the entity by primary key field and the value of the parameter being passed with the request.

Important

The annotation approach does not require you to have a find repository method whatsoever. It is a built-in behavior provided the Glagol DSL runtime environment.

Store action

The store method is there to handle POST requests aimed to create new resources. Additionally, the Glagol::Http::Request object is necessary for extracting input POST data from the request:

namespace MusicLib

import Glagol::Http::Request;

rest controller /song {

    repository<Song> songs = get selfie;

    index {
        return songs.findAll();
    }

    show (@autofind Song song) {
        return song;
    }

    store (Request request) {
        Song song = new Song(request.input("title"), request.input("author", "unknown"));

        songs.save(song);
        return song;
    }
}

Do not forget to compile the source!

Hint

The Glagol::Http::Request object is provided by the Glagol DSL standard library. It provides the following methods for extracting POST data:

string input(string key);
string input(string key, string default);

Use the string default parameter to provide a default value if the string key does not exist in the request payload.

Since Glagol DSL relies on Lumen Framework for its runtime both json and x-www-form-urlencoded payloads are supported. For this example we are going to use a json payload:

$ curl -X POST -d '{"title":"Born to raise hell", "author": "Motorhead"}' -H "Content-Type: application/json" localhost:8081/song

Update action

The update action is similar to both show and store actions in a way. First, just like show it requires a parameter by which to identify a resource. Secondly, just like store it can use the input() methods from Glagol::Http::Request.

In contrast to both actions, the HTTP request method for update is PUT. Lets look at this example:

namespace MusicLib

rest controller /song {

    repository<Song> songs = get selfie;

    index {
        return songs.findAll();
    }

    show (@autofind Song song) {
        return song;
    }

    store (Request request) {
        Song song = new Song(request.input("title"), request.input("author", "unknown"));

        songs.save(song);
        return song;
    }

    update (Request request, @autofind Song song) {
        song.setTitle(request.input("title"));
        song.setAuthor(request.input("author", "unknown"));

        songs.save(song);

        return song;
    }
}

Additionally, the example above requires two setter methods in the entity:

namespace MusicLib

@table="songs"
entity Song {
    // ... properties and constructors from before...

    public void setTitle(string title) {
        this.title = title;
    }

    public void setAuthor(string author) {
        this.author = author;
    }
}

Compile the sources and test with curl:

$ curl -X PUT -d '{"title":"Killed by death", "author": "Motorhead"}' -H "Content-Type: application/json" localhost:8081/song/2

Delete action

Glagol DSL provides the delete action to handle DELETE HTTP requests:

namespace MusicLib

rest controller /song {

    repository<Song> songs = get selfie;

    index {
        return songs.findAll();
    }

    show (@autofind Song song) {
        return song;
    }

    store (Request request) {
        Song song = new Song(request.input("title"), request.input("author", "unknown"));

        songs.save(song);
        return song;
    }

    update (Request request, @autofind Song song) {
        song.setTitle(request.input("title"));
        song.setAuthor(request.input("author", "unknown"));

        songs.save(song);

        return song;
    }

    delete (@autofind Song song) {
        songs.remove(song);
    }
}

Compile the sources and test with curl:

$ curl -X DELETE localhost:8081/song/1

Create and edit actions

Additionally, you can also implement the create and edit actions that are basically responding to GET requests but with additional route extensions.

First, the create action is usually used to return an initial state entity. Typically this functionality is used to retrieve a blank resource from the service:

namespace MusicLib

rest controller /song {

    repository<Song> songs = get selfie;

    index {
        return songs.findAll();
    }

    show (@autofind Song song) {
        return song;
    }

    store (Request request) {
        Song song = new Song(request.input("title"), request.input("author", "unknown"));

        songs.save(song);
        return song;
    }

    update (Request request, @autofind Song song) {
        song.setTitle(request.input("title"));
        song.setAuthor(request.input("author", "unknown"));

        songs.save(song);

        return song;
    }

    delete (@autofind Song song) {
        songs.remove(song);
    }

    create {
        return new Song("Please, enter song title", "Please, enter song author (band)");
    }

    edit (@autofind Song song) {
        return song;
    }
}

To get the response from this endpoint you need to append /create to the route like this:

$ curl localhost:8081/song/create

Similarly, edit is just like show but it requires the /edit extension to be accessed:

$ curl localhost:8081/song/1/edit