Vapor — Routes groups and collections

Organize your routes a bit further

Alexandre Cools
6 min readNov 2, 2020

Using Controllers to organise our routes is a big thing, but we can go a bit more further by grouping or routes and using collections.

Why always organise our routes?

Because we’re creating an API, the routes is all our system. When we have to fix some bugs (because we always have to fix some bugs), it’s important to know where we can find the function we need to fix.

Well, put all our code in the same file, and then use Cmd + F (or Ctrl + F)

It’s a form of organisation, but there are many disadvantages with this system, but that’s not the subject of this article.

By grouping routes, we will be able to update our code a bit later in the future if needed, and also it’s more easier to keep consistency for all of our routes.

Difference between groups and collections

Groups

Grouping routes allow us to keep the same route prefix, it’s useful when we have many routes who start by the same sub-path.

Example:

  • /users — Get all users
  • /users/{uuid} — Get one specific user
  • /users/{uuid}/workouts — Get specific user workouts
  • /users/{uuid}/workouts/{workoutUuid} — Get specific workout for a user

When we have some routes like this, it could be a good idea to create a group because all of this routes is for users, and all of them start by /users.

By creating a group, we’ll be able to define our starting path (in this case /users), then our routes will simply be:

  • /
  • /{uuid}
  • /{uuid}/workouts
  • /{uuid}/workouts/{workoutUuid}

Also, in the future, if you want to rename your routes (because you’ve made a mistake in the name for example), you can update the value in one place and all of your routes will be updated (if you don’t have groups, maybe you will miss one or two routes, and then you loose consistency).

Collections

Collections are sub-routers. So instead of defining all of your routes in the routes.swift file (your main router), you can divide your routes in multiple routers.

It’ pretty the same as controllers, but with some advantages. Both controllers and collections allow you to separate your code into multiple files, but the difference between the two is that, with controllers, you have to register all yours routes in the routes.swift file .

While, with collection, you can define it directly on one line, and then define your routes in your collection file.

So it’s a bit nicer because you define all your sub routers in the routes.swift file, and then you can manage all your routes in yours sub routers without touching the routes.swift file.

Organisation of a collection (router in green, sub-routers in red and routes in yellow)

Create a group

To create a group, here is the syntax:

app.group("YOUR_GROUP_NAME") { routesBuilder in
// Your routes here
}

Don’t forget to replace YOUR_GROUP_NAME by what you want, and keep in mind it’s part of the final route. So if for example, you do the following:

app.group("users") { routesBuilder in
// Your routes here
}

All your routes in this group will begin by /users/.

💡 Tips: You can define your route directly in this group, or you can also use Controller to organise all of your code.

Create a collection

Collections, like Controllers allow us to separate or code in another file that the routes.swift file, so we can start by creation a new file called UserCollection.swift.

Then, a collection is nothing else that a struct conformed to the RouteCollection protocol.

Here’s the basic structure:

struct UserCollection: RouteCollection {}

To conform the struct to the protocol, you have to implement a single function:

func boot(routes: RouteBuilder) throws {}

This function is the equivalent of the routes(_ app: Application) throws function of the routes.swift file. It’s logic, because the routes function is our main router, and a collection is like a sub-router, we need so an entry point like a normal router.

Now, because the collection is like a router, you can use the boot function exactly the same way as the routes function of the routes.swift file.

You can define route directly in it:

func boot(routes: RoutesBuilder) throws {
routes.get(“hello”, “:username”) { request -> String in
// Your code here
return “”
}
}

Or even use groups inside your collection:

func boot(routes: RoutesBuilder) throws {
routes.group(“users”) { group in
group.get(“hello”, “:username”) { request -> String in
// Your code here
return “”
}
}
}

Once you have defined your collection, you have to inform your main router you want to use it as sub-router. You can register this collection in your main controller:

func routes(_ app: Application) throws {
try? app.register(collection: UserCollection())
}

By doing this, all of the routes in your UserCollection will be register to your main controller and so usable like if you define your routes one by one (like using Controllers).

Groups, collections, controllers

Photo by Brendan Church on Unsplash

What should you choose between groups, collections and controllers? You should probably use all of them. In fact, it’s like a toolbox who allow you to organise your code in different level of separation.

(I say you should probably use all of them, but it also depend of your project, for sure)

Personally, I think for complex project, it’s nice to use all of them (or at least groups and collections; you can easily use your collection as controller if you want).

Here’s an example of using collection, group and controller (all of the code is placed in the routes.swift file but you can (and must) separate the code in multiple files):

func routes(_ app: Application) throws {
try? app.register(collection: UserCollection())
}
struct UserCollection: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.group(“users”) { group in
let
userController: UserController = UserController()
group.get(“:username”, “hello”, use: userController.getWelcome)
group.get(“:username”, “score”, use: userController.getScore)
}
}
}
struct UserController {
func getWelcome(request: Request) -> String {
“”
}
func getScore(request: Request) -> String {
“”
}
}

So, first, you have your entry point (the routes(_app: Application) function), then, a Collection (UserCollection) as sub-router who will be used to manage all users routes. Finally, there is a group (you can have more if needed) for all routes who start with /users/, and a Controller is used to keep all users routes functions in the same file.

Personally, I think this is a little too much, but you can use it this way if you want.

Here is what I like to use. It’s the same thing in the principle, but only by using Collection and Group (I said you right before that Controllers could be replaced by Collections, here we are):

func routes(_ app: Application) throws {
try? app.register(collection: UserCollection())
}
struct UserCollection: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.group(“users”) { group in
group.get(“:username”, “hello”, use: getWelcome)
group.get(“:username”, “score”, use: getScore)
}
}
}
extension UserCollection {
func getWelcome(request: Request) -> String {
“”
}
func getScore(request: Request) -> String {
“”
}
}

The structure is pretty the same, we only replaced the controller by an extension of the collection, and by doing this we don’t need to instantiate a Controller anymore.

🚀 What’s next?

Don’t forget to “clap” and share this article if you like it and want to see more content like this! 👏

If you have another way to use routes collections and/or groups, or if you have any question, I’ll be happy to read you in the comments section right bellow.

Thanks for reading,

--

--

Alexandre Cools

iOS Developer @LunabeeStudio 👨🏻‍💻 — Sport addict 🏊‍♂️🚴‍♂️🏃‍♂️ — Travel enthusiast ✈️