
This video is only available to subscribers. Start a subscription today to get access to this and 484 other videos.
Vapor Routing
This episode is part of a series: Server-side Swift with Vapor.
1. Getting Started with Vapor 12 min |
2. Vapor Routing 14 min |
3. Leaf Templates 13 min |
4. Nesting Templates and Partials 8 min |
5. Vapor Demo: Tokenizr 21 min |
6. Setting up a Database with Fluent 19 min |
7. Creating, Updating, and Deleting Records with Fluent 19 min |
8. Vapor Futures 20 min |
9. Setting up Vapor with Postgresql 17 min |
10. UUID Primary Keys 13 min |
11. Timestamp Fields 4 min |
12. Refactoring to Protocols 12 min |
13. Parent Child Relationships and Foreign Keys 14 min |
14. Pivot Tables for Many to Many Relationships 14 min |
15. Vapor Controllers 15 min |
16. Decoding Request Parameters 9 min |
Episode Links
Simple Routes
Matching the root route:
// match /
router.get { req in
return "This is the root route"
}
It is important to note that we're returning a String
here, but it could be other types of responses as well. It's usually a good idea to help the compiler out and specify the return type explicitly, which can help avoid confusing errors and longer compile times.
// match /
router.get { req -> String in
return "This is the root route"
}
Matching a path:
// match /home
router.get("home") { req -> String in
return "This is the home page"
}
Returning JSON
To return JSON, we need to create an object that conforms to the Content
protocol. This protocol also includes the Codable
protocol, so we need to make sure our types conform to that as well.
struct Status : Content {
var message: String
}
Then we can return an instance of this, and it will get converted for us automatically.
router.get("status") { req -> Status in
return Status(message: "This is a custom JSON response")
}
If we build and run, then hit this route in the browser, we'll see this response:
{
"message": "This is a custom JSON response"
}
Pretty easy!
Accepting JSON
We can accept JSON as input with a post request as well. Note that these have the same path, but will be matched based on the HTTP method used as well (GET vs POST).
router.post("status") { req -> String in
// try to decode the body
let status = try request.content.syncDecode(Status.self)
return status.message
}
Here we use the try
keyword, because the body may not be in the format we are expecting. We then call syncDecode
, which halts the process to decode the body. Later on we can look at decode
, which does this asynchronously.
To test this out, we can use curl
from the command line:
$ curl -X POST http://localhost:8080/status -H 'Content-Type: application/json' -d'{"message":"This is a custom post request"}'
It's important to pass the Content-Type
header here, otherwise the body will be treated as form encoded, which would look like this: message=this%20is%20my%20custom%20message
. This also works, but is less common for APIs. For websites, this is how forms will post data back to the server, and it's handy that Vapor treats both equally. In other words, the client can choose which format and specify via the header, the backend will react accordingly.
For more complex route testing, I recommend using Paw.
Using method handles as routes
Sometimes we want to have 2 routes that have the same logic. For this we can define a method that handles the route:
func handleAboutPage(_ req: Request) -> String {
return "This is the About page"
}
Then we can reference the method as a paramter in our route definitions:
router.get("about", use: handleAboutPage)
router.get("about_us": handleAboutPage)
Route Parameters
Let's say we want a dynamic part of the route, like this:
/users/ben
/users/mary
To handle these, we need to tell Vapor which part of the route is dynamic.
router.get("users", String.parameter) { req -> String in
let username = try req.parameters.next(String.self)
return "Hi there, \(username)"
}
The first line tells Vapor how to match
the route. It will match things like /users/foo
and /users/123
, but won't match /users
because there isn't a 2nd route parameter.
Why does it match users/123
? Because "123"
is a valid string!
If we only wanted to match integers, we could do this:
router.get("users", Int.parameter) { ... }
And then the route would match /users/123
but not /users/ben
.
Next we pull the parameter out of the URL. You can think of this as a stack of values, and when you call next(..)
you're popping an item off the stack.
Returning Errors
Sometimes we don't have enough input to continue, or we encounter an error we can't recover from. For these we need a way to bail out and return an error response to the client (rather than allowing the server to crash).
Vapor leans on Swift errors and throw
for this.
router.get("boom") { req -> String in
throw Abort(.internalServerError)
}
There are other types of errors you might want to throw, and this will drive what HTTP status code the client receives.