Show Navigation

Grails URL Mappings

Learn to configure routes and endpoints

Authors: Zachary Klein

Grails Version: 4.0.1

1 Getting Started

Whether you’re building a traditional page-based application, a Single-Page App, or a microservice, having a logical, extendable API is a very important part of application design. Grails offers powerful support for building APIs with URL Mappings.

The sample application we are going to build will be a conference scheduling app, called agenda. We will have a simple domain model consisting of Speakers, Talks, an Agenda (which ties together speakers and talks), and a Conference (which has an agenda). We won’t be creating the domain model in this guide, but we’ll create the web layer that will sit above it for now.

1.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

1.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/grails_url_mappings/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/grails_url_mappings/complete

2 Creating our Application

Please choose from the following options to create your project:

1. Create a new Application via the Grails CLI

If you have Grails (>= 3.3.0) installed, you can create your application on the command line:

~ grails create-app agenda

2. Create a new Application via the Application Forge

You can download your project from http://start.grails.org, either by using the online wizard, or the following curl command:

~ curl -O start.grails.org/agenda.zip -d version=3.3.2

3. Use the Initial Project from the Guide Repo

Within the guide directory, copy the initial project to a directory of your choice, rename it (e.g., agenda), and follow along with the guide from within that project.

3 Creating Controllers

Before we can define our URL mappings, we need to create some controllers. You can think of them as the "web layer" of our application. Controllers respond to requests (i.e., from a user’s browser, or from a another microservice), obtain data from the model, and return a response, such as an HTML page or a JSON string.

You can create a controller by simply creating a Groovy class under the grails-app/controllers directory. However, Grails provides a handy command for creating controllers on the command-line.

Because we’re going to create several controllers, let’s use the interactive mode provided by the Grails CLI. Run the following command from within your project directory:

~ ./grailsw
...

grails >

Once the interactive CLI is loaded up, you can run any available Grails commands, including create-controller. Run the following commands:

grails > create-controller demo.Talk
grails > create-controller demo.Speaker
grails > create-controller demo.Agenda
grails > create-controller demo.Conference

Go head and quit the Grails CLI:

grails > exit

Now that we have our controllers, it’s time to create actions which will be exposed as endpoints for users of our application (whether humans or perhaps a client of our API). Actions are simply methods defined in a controller class. In a CRUD (*C*reate, *R*ead, *U*pdate, *D*elete) controller, you will typically want actions such as shown below:

class CrudController {

    def index() {} //usually a "list" view

    def save() {} //save new records

    def show(Serializable id) {} //retrieve details on a specific record

    def update(Serializable id) {} //update a specific record

    def delete(Serializable id) {} //delete specific record
}

If this was a REST controller, the above actions might be sufficient for CRUD operations. If we wanted to also display HTML forms for editing and creating records, we might also add create and edit actions to display those pages.

Let’s stub out CRUD actions for our (future) domain model. Edit the first three controllers (TalkController, SpeakerController, and AgendaController) as shown below:

grails-app/controllers/demo/TalkController.groovy
package demo

class TalkController {

    def index() {
        render "Retrieving all Talks..."
    }

    def save() {
        render "Saving new Talk..."
    }

    def show(Long id) {
        render "Retrieving Talk ${id}..."
    }

    def update(Long id) {
        render "Updating Talk ${id}..."
    }

    def delete(Long id) {
        render "Deleting Talk ${id}..."
    }
}
grails-app/controllers/demo/SpeakerController.groovy
package demo

class SpeakerController {

    def index() {
        render "Retrieving all Speakers..."
    }

    def save() {
        render "Saving new Speaker..."
    }

    def show(Long id) {
        render "Retrieving Speaker ${id}..."
    }

    def update(Long id) {
        render "Updating Speaker ${id}..."
    }

    def delete(Long id) {
        render "Deleting Speaker ${id}..."
    }
}
grails-app/controllers/demo/AgendaController.groovy
package demo

class AgendaController {

    def index() {
        render "Retrieving all Agendas..."
    }

    def save() {
        render "Saving new Agenda..."
    }

    def show(Long id) {
        render "Retrieving Agenda ${id}..."
    }

    def update(Long id) {
        render "Updating Agenda ${id}..."
    }

    def delete(Long id) {
        render "Deleting Agenda ${id}..."
    }
}

For the ConferenceController, we’ll create a different set of actions - talks, speakers, and agenda. In addition, the talks and speakers actions will accept an optional id parameter, so that we can display a specific speaker or talk.

Edit ConferenceController.groovy as shown below:

grails-app/controllers/demo/ConferenceController.groovy
package demo

class ConferenceController {

    def talks(Long id) {
        if(id) {
            render "Returning conference talk ${id}..."
        } else {
            render "Returning conference talks..."
        }

    }

    def speakers(Long id) {
        if(id) {
            render "Returning conference speaker ${id}..."
        } else {
            render "Returning conference speakers..."
        }
    }

    def agenda() {
        render "Returning conference agenda..."
    }
}

Now that we have our controller actions stubbed out, let’s see what Grails URL mappings have given us out of the box.

4 Defining URL Mappings

Let’s look at the default UrlMappings.groovy file (under grails-app/controllers/demo):

package demo

class UrlMappings {

    static mappings = {
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }

        "/"(view:"/index")
        "500"(view:'/error')
        "404"(view:'/notFound')
    }
}

The very first mapping in the mappings block is pretty straightforward. It has two components: a string, defining the URL pattern, and a closure, where options (such as constraints) can be specified to change the behavior of the mapping.

See how to use URL constraints here in the Grails documentation.

The URL pattern can include variables, which are denoted by Groovy Strings within the pattern (e.g., $controller or ${controller}. Grails has a few special wildcards which it supports out of the box. For example, $controller will be mapped to the name of a given controller, $action will be mapped to an action within that controller, and so forth. Optional variables are suffixed with a ?. E.g., /controller/$action?

Using the variables for all the parts of the URL pattern means this particular mapping will match any URL. See the table below:

Table 1. URL Mapping Resolution
URL Result

/talk/show/1

TalkController, show action, id parameter of 1

/talk/

TalkController, default action (typically an`index` action)

/speaker/show/1.json

SpeakerController, show action, id parameter of 1, format parameter of json (can be used for content-negotiation)

In addition to mapping to controller actions, URL mappings can also map directly to a view (without specifying a controller or action), or an arbitrary URI (useful for redirects). For example, the default URL mapping for the root context, /, points to the default index.gsp page under grails-app/views:

    "/"(view:"/index")

You can use the uri argument to map to a different URI:

    "/hello"(uri: "/hello.dispatch")

Another useful kind of mapping are HTTP error code mappings, also seen in the default UrlMappings.groovy file. The default mappings simply load a default error view, however you could easily redirect these requests to an error-handling or logging controller to apply custom error handling logic and recovery in your app. E.g.,

    "500"(controller:'myErrorController', action: 'somethingBroke')
    "404"(controller:'myErrorController', action: 'notFound')

The default URL mappings will cover most of our needs in this sample app. However, to give us finer grained control, we are going to add a few of our own mappings for the ConferenceController actions we stubbed out above.

The default set of URL mappings cover many use cases. However, when it’s time to secure your application, you may want to remove the default mapping and replace it with more specific mappings (so that users cannot access any controller action they like simply by figuring out the names).

Edit the UrlMappings.groovy file as shown below:

grails-app/controllers/demo/UrlMappings.groovy
package demo

class UrlMappings {

    static mappings = {
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }


        "/talks/$id?"(controller: 'conference', action: 'talks') (1)

        "/speakers/$id?"(controller: 'conference', action: 'speakers')

        "/agenda"(controller: 'conference', action: 'agenda') (2)


        "/"(view:"/index")
        "500"(view:'/error')
        "404"(view:'/notFound')
    }
}
1 We are mapping a custom URL pattern /talks/$id? to our ConferenceController actions. The $id variable means that this component of the URL pattern will be mapped to the id parameter of the talks action. The suffix of ? makes this variable optional.
2 Note that we’re not using any variables in this patterns, so the request must match the URL pattern exactly. Otherwise, it will be handled by the default URL mapping at the top of the file.

If you start up the application now and make a request to http://localhost:8080/talks, you should see this text rendered to the browser:

    Returning conference talks...

Try another URL: http://localhost:8080/talks/1:

    Returning conference talk 1...

HTTP Methods

There’s one more feature to note on URL mappings before we move on. In addition to specifying URL patterns and constraints, mappings can also specify the specific HTTP method required to match the mapping. This is as simple as prefixing the pattern with the required HTTP verb, such as get, post, or put.

Let’s update our UrlMappings.groovy file one more time, to specify that we only accept GET requests to our ConferenceController actions.

grails-app/controllers/demo/UrlMappings.groovy
package demo

class UrlMappings {

    static mappings = {
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }

        get "/talks/$id?"(controller: 'conference', action: 'talks')
        get "/speakers/$id?"(controller: 'conference', action: 'speakers')
        get "/agenda"(controller: 'conference', action: 'agenda')


        "/"(view:"/index")
        "500"(view:'/error')
        "404"(view:'/notFound')
    }
}

If you now use curl (or another HTTP client) to make a non-GET request to one of these URLs, you’ll receive a 404 page. Try it with the below curl command:

~ curl -X PUT localhost:8080/talks

5 Organize URL Mappings

Over time, your applications might end up with many similar URL mappings, or large numbers of mappings that are specific to one part of the app. There are a few ways to organize URL mappings, including the group method, as well as using multiple UrlMapping.groovy files.

URL Groups

If you have a large number of mappings that fall under a particular path, you can use the group method to specify the shared portion of the URL pattern, and list the right-hand portion of each pattern separately.

Edit the UrlMappings.groovy file again, and group the three URL mappings we’ve added under a shared path: /conf.

grails-app/controllers/demo/UrlMappings.groovy
package demo

class UrlMappings {

    static mappings = {
        //...

        group "/conf", {
            get "/talks/$id?"(controller: 'conference', action: 'talks')
            get "/speakers/$id?"(controller: 'conference', action: 'speakers')
            get "/agenda"(controller: 'conference', action: 'agenda')
        }

        //...
    }
}

If you restart the application, you can see that these mappings now require /conf as the first part of the URL pattern. E.g., http://localhost:8080/conf/talks

Multiple UrlMappings files

Grails supports multiple UrlMappings files. The only requirements are that each file must be uniquely named, reside within grails-app/controllers, and the file name must end with UrlMappings.groovy.

Let’s create a second UrlMappings file for our ConferenceController mappings. Create a new file named ConferenceUrlMappings.groovy, end edit its contents as shown below:

grails-app/controllers/demo/ConferenceUrlMappings.groovy
package demo

class ConferenceUrlMappings {

    static mappings = {

        group "/conf", {
            get "/talks/$id?"(controller: 'conference', action: 'talks')
            get "/speakers/$id?"(controller: 'conference', action: 'speakers')
            get "/agenda"(controller: 'conference', action: 'agenda')
        }
    }
}

Now, delete the /conf URL mappings from the original UrlMappings.groovy file, so it matches the version below:

grails-app/controllers/demo/UrlMappings.groovy
package demo

class UrlMappings {

    static mappings = {
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }

        "/"(view:"/index")
        "500"(view:'/error')
        "404"(view:'/notFound')
    }
}

6 Testing URL Mappings

The Grails Testing Support library is the recommended way to write tests for most Grails artefacts, including URL mappings. The library is included in the current version of Grails, and it provides a UrlMappingsUnitTest trait that makes it easy to test URL mappings.

Traits are a powerful feature of the Groovy programming language, and many Grails features utilize them. You can create your own traits as well as part of your application code. See the Groovy documentation for more information.

Let’s write a unit test for our ConferenceUrlMappings. Create a new Groovy file under src/test/groovy/demo/, named ConferenceUrlMappingsSpec.groovy. Edit the file with the content below:

src/test/groovy/demo/ConferenceUrlMappingsSpec.groovy
package demo

import grails.testing.web.UrlMappingsUnitTest
import spock.lang.Specification

class ConferenceUrlMappingsSpec extends Specification implements UrlMappingsUnitTest<ConferenceUrlMappings> { (1)

    void setup() {
        mockController(ConferenceController) (2)
    }

    void "test conference mappings"() {

        expect: "calls to /conference/talks to succeed"
        verifyUrlMapping("/conf/talks", controller: 'conference', action: 'talks', method: 'GET')  (3)
        verifyUrlMapping("/conf/talks/1", controller: 'conference', action: 'talks', method: 'GET') {
            id = '1'
        }

        when: "calling /conf/speakers"
        assertUrlMapping("/conf/speakers", controller: 'conference', action: 'speakers', method: 'GET') (4)

        then: "no exception is thrown"
        noExceptionThrown()

    }

}
1 Our test is a standard Spock Specification, which then implements the UrlMappingsUnitTest trait (provided by the testing support library). Note that the trait accepts a generic, which is the URL mapping class that we wish to test; in this case, ConferenceUrlMappings
2 The testing library provides mocking helpers, such as mockController to wire up an instance of a controller for testing.
3 The UrlMappingsUnitTest trait provides several testing methods, including verifyUrlMapping, which simply returns true or false if the URL mapping matches the expected controller/action.
4 Another test method is assertUrlMapping. This performs the same check as verifyUrlMapping, but it allows you to use the method as part of assertion (such as a Spock when/then block).

7 Conclusion

At this point you should be comfortable with the concept of URL mappings, and you have a good handle on the most important features. You can learn much more about the capabilities of URL mappings from the Grails Documentation, including namespaces, RESTful resource mappings, constraints, and wildcards.

8 Help with Grails

OCI sponsored the creation of this Guide. OCI offers several Grails services:

Free consultation

The OCI Grails Team includes Grails co-founders, Jeff Scott Brown and Graeme Rocher. Check our Grails courses and learn from the engineers who developed, matured and maintain Grails.

Grails OCI Team