Controllers
Controllers help you organize related functionality into a single place. They can also be used to create RESTful resources.
Basic
A basic controller looks like the following:
final class HelloController {
func sayHello(_ req: Request) throws -> ResponseRepresentable {
guard let name = req.data["name"] else {
throw Abort.badRequest
}
return "Hello, \(name)"
}
}
Simple controllers don't need to conform to any protocols. You are free to design them however you see fit.
Registering
The only required structure is the signature of each method in the controller. In order to register this method into the router, it must have a signature like (Request) throws -> ResponseRepresentable
. Request
and ResponseRepresentable
are made available by importing the HTTP
module.
let hc = HelloController()
drop.get("hello", handler: hc.sayHello)
Since the signature of our sayHello
method matches the signature of the closure for the drop.get
method, we can pass it directly.
Type Safe
You can also use controller methods with type-safe routing.
final class HelloController {
...
func sayHelloAlternate(_ req: Request, _ name: String) -> ResponseRepresentable {
return "Hello, \(name)"
}
}
We add a new method called sayHelloAlternate
to the HelloController
that accepts a second parameter name: String
.
let hc = HelloController()
drop.get("hello", String.self, handler: hc.sayHelloAlternate)
Since this type-safe drop.get
accepts a signature (Request, String) throws -> ResponseRepresentable
, our method can now be used as the closure for this route.
Resources
Controllers that conform to ResourceRepresentable
can be easily registered into a router as a RESTful resource. Let's look at an example of a UserController
.
final class UserController {
func index(_ request: Request) throws -> ResponseRepresentable {
return try User.all().makeNode().converted(to: JSON.self)
}
func show(_ request: Request, _ user: User) -> ResponseRepresentable {
return user
}
}
Here is a typical user controller with an index
and show
route. Indexing returns a JSON list of all users and showing returns a JSON representation of a single user.
We could register the controller like so:
let users = UserController()
drop.get("users", handler: users.index)
drop.get("users", User.self, handler: users.show)
But ResourceRepresentable
makes this standard RESTful structure easy.
extension UserController: ResourceRepresentable {
func makeResource() -> Resource<User> {
return Resource(
index: index,
show: show
)
}
}
Conforming UserController
to ResourceRepresentable
requires that the signatures of the index
and show
methods match what the Resource<User>
is expecting.
Here is a peek into the Resource
class.
final class Resource<Model: StringInitializable> {
typealias Multiple = (Request) throws -> ResponseRepresentable
typealias Item = (Request, Model) throws -> ResponseRepresentable
var index: Multiple?
var store: Multiple?
var show: Item?
var replace: Item?
var modify: Item?
var destroy: Item?
var clear: Multiple?
var aboutItem: Item?
var aboutMultiple: Multiple?
...
}
Now that UserController
conforms to ResourceRepresentable
, registering the routes is easy.
let users = UserController()
drop.resource("users", users)
drop.resource
will take care of registering only the routes that have been supplied by the call to makeResource()
. In this case, only the index
and show
routes will be supplied.
Note:
drop.resource
also adds useful defaults for OPTIONS requests. These can be overriden.
Folder
Controllers can go anywhere in your application, but they are most often stored in the Controllers/
directory.
Modules
If you are building a large application, you may want to create your controllers in a separate module. This will allow you to perform unit tests on your controllers. For more information on creating modules, visit the documentation for the Swift Package Manager.