Routing in Vapor, Part 2: Route Collections and Controllers

386 Views

Part 2 of this series will focus on better organization of our routes using Route Collections, controllers, and using the Parameterizable protocol.

First, download the starter project here. It is just a bare bones project with a Post class, and the Post class has 2 properties: an id, and a title.

The PostController class has 2 methods: create and all, for creating a post and retrieving all posts, respectively.

Adding Routes in Controllers

We know that all routes are called off of an instance of Droplet in Vapor, so in order create routes in our controllers, we must be able to access our droplet instance in our controller class. We will do that by using it as parameter in our function to add routes. In PostController.swift, add the following function:

    func addRoutes(to drop: Droplet) { // (1)
        drop.get("all", handler: all) // (2)
        drop.post("create", handler: create)
    }

In (1), we make the drop variable a parameter, so we can import our instance to add routes. In (2), we call the get method on drop to indicate it will be a GET request, and pass it a route (`"all"`), and the handler parameter refers to the name of the function that will receive this request. We do the same below, except create will be a POST request, so we call drop.post.

Now, in Routes.swift, add the following to your setupRoutes() method:

    let postController = PostController()
    postController.addRoutes(to: self)

At this point, you should be able to make a POST request to `http://localhost:8080/create`, and create a post. You should receive a JSON response such as:

    {
      "id": 1,
      "title": "this is a title"
    }

Navigating to http://localhost:8080/all should show all your posts in an array.

Good! This works, and it is better to organize our routes in a controller so we don't pollute the setupRoutes() function. But, what if, instead of navigating to /create and /all, we want to create a group so we can access these at /posts/create and /posts/all? Accessing posts via a /posts route prefix would seem more appropriate, right? As discussed in Part 1, it's simple using the grouped("") method on our droplet instance. Change your addRoutes() function to read:

    func addRoutes(to drop: Droplet) {
        let grouped = drop.grouped("posts")
        grouped.get("all", handler: all)
        grouped.post("create", handler: create)
    }

Now, we can be more precise about our routes by organizing them into groups!

Route Collections

Organizing routes this way, in controllers, works well for small to mid-size applications. But many larger apps need to be more centrally controlled and organized. For example, what if you have an entire API, and you want every route prefixed with v1? Or what if you have a a REST api and a web api, and different routes for each? Needing control on a larger scale is where a RouteCollection comes in. The examples in this tutorial are small, but can apply to larger applications. Using RouteCollections allows us to organize our routes into separate modules and files.

Create a class, called V1:

    class V1: RouteCollection {

    }

You'll notice an error. To conform to the RouteCollection protocol, your class must implement the build method. Add the following to your V1 class:

    func build(_ builder: RouteBuilder) throws {
        
    }

Next, in your PostController, change the `addRoutes()` function to read:

    func addRoutes(to routeBuilder: RouteBuilder) {
         routeBuilder.post("create", handler: create)
        routeBuilder.get("all", handler: all)
    }

This time, we are adding the routes to the routeBuilder instance, and (2 steps later in this tutorial), the routeBuilder will pass them to our instance of Droplet, instead of us calling them directly on droplet.

Then, in the build function in your V1 class, add the following:

    let v1 = builder.grouped("api", "v1") // (1)
    let posts = v1.grouped("posts") // (2)
    let postController = PostController()
    postController.addRoutes(to: posts)
  • In (1), we great a group to nest our routes under /api/v1.
  • In (2), we create a posts group, to be nested under our api/v1 group. All routes under this group will start with /api/v1/posts
  • In (3) we add the routes to our instance of RouteBuilder, posts.

Finally, in our setupRoutes() function, we can simply call:

    let v1 = V1()
    try collection(v1)

And that sets up our routes. Using Collections is nice, because it allows for cleaner organization of our code. For example, if we theoretically also had a User class, and we wanted to prefix all our routes by the api/v1 as above, we could simple append:

    let users = v1.grouped("users") 
    let userController = UserController()
    userController.addRoutes(to: users)

all without having to manually prepend "api/v1" to our routes, which could easily lead to typos and errors.


Parameterizable

Add the following function to your PostController.swift:

    func show(request: Request) throws -> ResponseRepresentable {
        let post = try request.parameters.next(Post.self)
        return post
    }

We can use this syntax because Vapor models conform to the Parameterizable protocol--meaning they can be used as parameters in routes. Under the hood, Vapor will use the parameter we pass and look up the model for us. We simply pass in the parameter type, as shown below.

In the same file, add to your addRoutes() function:

    routeBuilder.get(Post.parameter, handler: show)

The route for this will look like:

    /api/v1/posts/

The typical way of retrieving a single object in most frameworks is to pass the id of the object in the params, and then parse the id from the params, and then manually search the database. Vapor does all this for us, making our show function just 2 clean and concise lines of code.

That's all for part 2 of this series. In the third part, we will focus on adding middleware to routes and a bit more code organization. You can find the project completed to this point here.

Thanks for reading!



Enjoy this article? Consider supporting VaporForums by following us on Twitter! Get the latest Vapor articles, tutorials, and news.