From 72fb35cf082d19a55ffb1c21b70846b4e12c0d42 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Tue, 20 Sep 2016 13:29:41 -0400 Subject: [PATCH] controllers --- couscous.yml | 3 + guide/controllers.md | 139 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 guide/controllers.md diff --git a/couscous.yml b/couscous.yml index d9bc4a91..2d6e1cd9 100644 --- a/couscous.yml +++ b/couscous.yml @@ -47,6 +47,9 @@ menu: guide-views: text: Views relativeUrl: guide/views.html + guide-controllers: + text: Controllers + relativeUrl: guide/controllers.html guide-middleware: text: Middleware relativeUrl: guide/middleware.html diff --git a/guide/controllers.md b/guide/controllers.md new file mode 100644 index 00000000..4ff57ae0 --- /dev/null +++ b/guide/controllers.md @@ -0,0 +1,139 @@ +--- +currentMenu: guide-controllers +--- + +# 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: + +```swift +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`. + +```swift +let hc = HelloController() +drop.get("hello", 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. + +```swift +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`. + +```swift +let hc = HelloController() +drop.get("hello", String.self, 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`. + +```swift +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: + +```swift +let users = UserController() +drop.get("users", users.index) +drop.get("users", User.self, users.show) +``` + +But `ResourceRepresentable` makes this standard RESTful structure easy. + +```swift +extension UserController: ResourceRepresentable { + func makeResource() -> Resource { + return Resource( + index: index, + show: show + ) + } +} +``` + +Conforming `UserController` to `ResourceRepresentable` requires that the signatures of the `index` and `show` methods match what the `Resource` is expecting. + +Here is a peak into the `Resource` class. + +```swift +final class Resource { + 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. + +```swift +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](https://swift.org/package-manager/)