Tutorial: How to write Controllers

At the end of this article you’ll know how to implement and use controllers πŸ™ŒπŸ»

You can find the result of this tutorial on githubΒ here

This tutorial is a natural follow-up of How to write Models using Fluent. You can either go for that tutorial first and come back later or be a rebel, skip it and read on 😊

1. Create a newΒ project

We will use the outcome of the aforementioned tutorial as a template to create our new project:

vapor new projectName --template=vaporberlin/my-first-model

2. Generate XcodeΒ project

Before we generate an Xcode project we would have to change the package name within Package.swift:

import PackageDescription
let package = Package(
name: "projectName", // changed
products: [
.library(name: "App", targets: ["App"]),
.executable(name: "Run", targets: ["Run"])
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "2.1.0")),
.package(url: "https://github.com/vapor/leaf-provider.git", .upToNextMajor(from: "1.1.0")),
.package(url: "https://github.com/vapor/fluent-provider.git", .upToNextMajor(from: "1.3.0"))
],
targets: [
.target(name: "App", dependencies: ["Vapor", "LeafProvider", "FluentProvider"],
exclude: [
"Config",
"Public",
"Resources",
]
),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App", "Testing"])
]
)

Now in the terminal at the root directory projectName/ execute:

vapor xcode -y

It may take a bit fetching the dependency, but when done you should have a project structure like this:

projectName/
β”œβ”€β”€ Package.swift
β”œβ”€β”€ Sources/
β”‚ β”œβ”€β”€ App/
β”‚ β”‚ β”œβ”€β”€ Models/
β”‚ β”‚ β”‚ └── User.swift
β”‚ β”‚ β”œβ”€β”€ Routes/
β”‚ β”‚ β”‚ └── Routes.swift
β”‚ β”‚ └── Setup/
β”‚ β”‚ β”œβ”€β”€ Config+Setup.swift
β”‚ β”‚ └── Droplet+Setup.swift
β”‚ └── Run/
β”œβ”€β”€ Tests/
β”œβ”€β”€ Config/
β”œβ”€β”€ Public/
β”œβ”€β”€ Dependencies/
└── Products/

4. Why we need controllers

If you look into your Routes.swift you should see two functions (routes) from which the first would return a list of users and the second would create a new user. Imagine you would now want to implement the possibility to create short text snippets and also list them. You would probably have a route similar to get(β€œsnippet”) to get all snippets as a list and maybe a post(β€œsnippet”) to create new snippets. If you would do that in Routes.swift it would be kind of fine as long as your project is super small having only about 4 - 6 routes. As soon as your project gets bigger the Routes.swift file would get bigger too and you’ll start loosing overview. Also having one file taking care of multiple responsibilities is not a good practice πŸ€“. Like creating a user is something different than creating a snippet 😊.

5. Create your first controller

So what we want is to put everything that is user related into an own controller. An UserController. Create the following directory and file:

projectName/
β”œβ”€β”€ Package.swift
β”œβ”€β”€ Sources/
β”‚ β”œβ”€β”€ App/
β”‚ β”‚ β”œβ”€β”€ Controllers/ // added
β”‚ β”‚ β”‚ └── UserController.swift // added
β”‚ β”‚ β”œβ”€β”€ Models/
β”‚ β”‚ β”‚ └── User.swift
β”‚ β”‚ β”œβ”€β”€ Routes/
β”‚ β”‚ β”‚ └── Routes.swift
β”‚ β”‚ └── Setup/
β”‚ β”‚ β”œβ”€β”€ Config+Setup.swift
β”‚ β”‚ └── Droplet+Setup.swift
β”‚ └── Run/
β”œβ”€β”€ Tests/
β”œβ”€β”€ Config/
β”œβ”€β”€ Public/
β”œβ”€β”€ Dependencies/
└── Products/
NOTE: I used the terminal. In projectName/ execute mkdir Sources/App/Controllers/ and touch Sources/App/Controllers/UserController.swift

You may have to re-generate your Xcode project with vapor xcode -y in order to let Xcode see your new directory.

We want to have our get(β€œuser”) functionality within our UserController.swift therefor write the following code:

final class UserController {

func list(_ req: Request) throws -> ResponseRepresentable {
let list = try User.all()
return try self.view.make("userview", ["userlist": list.makeNode(in: nil)])
}
}

Let me for short explain. We are defining a new function calling it whatever we want, it makes sense to call it list since we are in a user controller and want this function to return a list of users. Functions that are meant to handle request always look like this:

func myFunc(_ req: Request) throws -> ResponseRepresentable {
...
}

Now how do we link a route like get(β€œuser”) in our Routes.swift to be handled by this function? It is so super simple, in your Routes.swift just do:

import Vapor
extension Droplet {
  func setupRoutes() throws {
    let userController = UserController()      // added
get("user", handler: userController.list) // added
    post("user") { req in
...
}
}
}
NOTE: We deleted the former route get(β€œuser”) here since it’s not longer needed

We initiate our user controller and define that get(β€œuser”) is handled by the user controllers function list. Now that we use Leaf we need to do one more thing. Before using the controller function we had get(β€œuser”) and called self.view.make(…) within it where self was referencing to Droplet. Now when we look into our UserController.swift within our list function, we still call self.view.make(…) but since we’re inside the UserController Class the self references to that class which is not having the view.make(…) function. All we have to do is to pass the Droplet into our class when initiating it, store it in a variable and call view.make(…) on it. In our UserController.swift add:

final class UserController {
let drop: Droplet
  init(drop: Droplet) {
self.drop = drop
}
  func list(_ req: Request) throws -> ResponseRepresentable {
let list = try User.all()
return try drop.view.make("userview", ["userlist": list.makeNode(in: nil)])
}
}

Now in our Routes.swift we pass the Droplet when initiating our controller:

import Vapor
extension Droplet {
func setupRoutes() throws {
let userController = UserController(drop: self)
get("user", handler: userController.list)
    post("user") {
...
}
}
}

If you now cmd+r or run and fire up the /user route it will just work!

Note: make sure to select Run as a scheme next to your button before running theΒ app

For the sake of completeness let’s move post(β€œuser”) to our controller too 😊
In our Routes.swift:

import Vapor
extension Droplet {
func setupRoutes() throws {
let userController = UserController(drop: self)
get("user", handler: userController.list)
post("user", handler: userController.create)
}
}

And then finally in our UserController.swift:

final class UserController {
let drop: Droplet
  init(drop: Droplet) {
self.drop = drop
}
  func list(_ req: Request) throws -> ResponseRepresentable {
...
}
  func create(_ req: Request) throws -> ResponseRepresentable {
guard let username = req.data["username"]?.string else {
return Response(status: .badRequest)
}
let user = User(username: username)
try user.save()
return Response(redirect: "/user")
}

}

And that’s it! You successfully implemented your first controller πŸŽ‰Β ! Whoop!


Thank you a lot for reading! If you have any questions or improvements - write a comment! I would love to hear from you! 😊

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.