Introduction to Routing in Vapor 3, Part 1

You must have at least Swift 4.1/Xcode 9.3 installed to complete this tutorial

This tutorial does not discuss Futures, but when working with database calls and querying (this tutorial doesn't), you will need to return Futures. Futures will be addressed in a future tutorial (pun intended).

Routing is at the heart of web frameworks, and it's easy with Vapor 3. Let's get started!

First, let's create a new project. Start by running the following command:

$ vapor new BasicRouting

Then, change directory into the project with $ cd BasicRouting , and then run $ vapor xcode to generate the Xcode project. Open the Xcode project when prompted.

The Simplest Route

We set up our routes in the routes(_ router: Router) method in routes.swift . All of our routes are created off of our instance of Router . Already set up for us, we can see the first method here:

router.get("hello") { req in return "Hello, world!" }

This is the most basic route, which creates a simple get request to /hello . We simply call .get() on our instance of Router , and the closure gives us access to the incoming request.

In such a simple instance, we may not need to explicitly state our return type, but for more complex routes, we need to explicitly state what we expect to return, like this:

router.get("hello") { req -> String in return "Hello, world!" }

Every route in vapor has 3 things:

a method (get, post, put, patch, delete)

a path, supplied by the user ( "hello" in our case)

a closure with the request

Also, we may notice that we returned a string. Every route closure in Vapor must return a Type conforming to ResponseEncodable . The most common types conforming to this are:

Response - an HTTPResponse object

Content - JSON, FormURLEncoded, or Multipart

View - a Leaf view

Nesting Routes

A hard coded nested route is quite simple:

router.get("hi", "how", "are", "you") { request in return "this path is /hi/how/are/you" }

Parameters

Let's make a more complicated route. Suppose we want to make a request to /cats/ , where id is an integer. We could write it like this:

router.get("cats", Int.parameter) { req -> String in let intParam = try req.parameters.next(Int.self) return "You have requested route /cats/\(intParam)" }

We can very conveniently get the param out with just one line of code, let intParam = try req.parameters.next(Int.self) .

So making a request to /cats/4 will yield a result of:

You have requested route /cats/4

Or how about a request to /cats/<id>/<String> ?

router.get("cats", Int.parameter, String.parameter) { req -> String in let intParam = try req.parameters.next(Int.self) let stringParam = try req.parameters.next(String.self) return "You have requested route /cats/\(intParam)/\(stringParam)" }

Making a request to /cats/5/grumpycat will yield a result of:

You have requested route /cats/5/grumpycat

Custom Responses

We can also return custom responses in our route handlers. How would we return a customized JSON response, for example?

First, create a struct or class which contains the fields you expect. For example, suppose we want to return a custom JSON like:

{"name":"anapaix", "apiToken": "kjubeufbfiubui9876bjhbuf"}

We would first create a struct, conform it to Content , and give it the data fields we expect:

struct UserResponse: Content { var name: String var apiToken: String }

Then, we create our route handler:

router.get("jsonTest") { req -> Response in // 1 let response = Response(http: HTTPResponse(status: .ok), using: req) // 2 let myContent = UserResponse(name: "anapaix", apiToken: "jlhbuoyb987Thgvihtyf") // 3 try response.content.encode(myContent, as: MediaType.json) // 4 return response }

There's a lot going on here, let's go over it step by step:

1 : We declare the route type (get), the path (/jsonTest), and the response type (Response)

: We declare the route type (get), the path (/jsonTest), and the response type (Response) 2 : We create the response object

: We create the response object 3 : We create a UserResponse object with the data we intend to pass back.

: We create a object with the data we intend to pass back. 4: we encode our response with the content, and set the MediaType to JSON

For 1, we could create any number of responses, for example:

Response(http: HTTPResponse(status: .ok), using: req) Response(http: HTTPResponse(status: .forbidden), using: req) Response(http: HTTPResponse(status: .created), using: req) Response(http: HTTPResponse(status: .unauthorized), using: req)

There are many others as well. See the HTTPStatus enum for all options.

Alternatively, if we are just looking to return the data, for name and apiToken , without a custom status code, we could write it as follows:

router.get("jsonTest2") { req -> UserResponse in return UserResponse(name: "anapaix", apiToken: "jlhbuoyb987Thgvihtyf") }

Query

Suppose we have a route, like /dogs?breed=husky ? How can we get the value from the breed ? In the same way that we created a struct to represent the data which we wished to return in our example above, we can create a struct to represent the data we expect to receive here. So if we expect one query parameter, breed , we can create a struct like so:

struct DogQuery: Content { var breed: String }

And then, for our query, we can make a get request to /dogs :

router.get("dogs") { request -> String in let dogQuery = try request.query.decode(DogQuery.self) return "the breed is \(dogQuery.breed)" }

And making a request to /dogs?breed=husky returns:

the breed is husky

So as you can see, once we have decoded the query string, we can access all of its values using simple dot notation. Isn't Vapor 3 wonderful? :D

Alternatively

Also, if you do not wish to create a struct, you can simply use the request.query.get(at: ) method to retrieve the content out of the query string:

router.get("bangkok") { request -> String in let bangkokQuery:String = try request.query.get(at: ["district"]) // 1 return "the query string here is \(bangkokQuery)" }

At 1, you must explicitly declare the type you expect the query string to be. A request to /bangkok?district=siam would yield:

the query string here is siam

Groups

Grouping routes is quite simple in vapor 3. Simply use the group("") method on our instance of router :

router.group("v1") { v1 in v1.get("users") { request in return "this route is /v1/users" } }

We can also combine groups and parameters:

v1.get("posts", Int.parameter) { request -> String in let parameter = try request.parameters.next(Int.self) return "You requested /v1/posts/\(parameter)" }

Well, that's it for this part 1 in this series! I'll try to keep this updated if anything changes between now and the official Vapor 3 release in mid March. Please leave any questions/comments below!