Tutorial: How to write Controllers

Martin Lasek
5 min readMar 29, 2018

--

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 😊

Index

1. Create a new project
2. Generate Xcode project
3. Why we need controllers
4. Create your first controller
5. Where to go from here

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:

// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "projectName", // changed
dependencies: [
// πŸ’§ A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
.package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc"),
.package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0-rc")
],
targets: [
.target(name: "App", dependencies: ["Vapor", "Leaf", "FluentSQLite"]),
.target(name: "Run", dependencies: ["App"]),
.testTarget(name: "AppTests", dependencies: ["App"]),
]
)

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

vapor update -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
β”‚ β”‚ β”œβ”€β”€ app.swift
β”‚ β”‚ β”œβ”€β”€ boot.swift
β”‚ β”‚ β”œβ”€β”€ configure.swift
β”‚ β”‚ └── routes.swift
β”‚ └── Run/
β”‚ └── main.swift
β”œβ”€β”€ Tests/
β”œβ”€β”€ Public/
β”œβ”€β”€ Dependencies/
└── Products/

In case you see an error with β€œCNIOOpenSSL” when running β€” you’re missing a dependency. Just run brew upgrade vapor and re-generate the project ✌🏻😊

3. Why we need controllers

If we look into routes.swift we should see two functions (routes) from which the first would return a view with 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 considered good practice πŸ€“. At the end a user is something different than a snippet 😊. Controllers to the rescue!

4. Create your first controller

What we want to do now is to put everything user related into an own controller. So go ahead and create the following directory and file:

projectName/
β”œβ”€β”€ Package.swift
β”œβ”€β”€ Sources/
β”‚ β”œβ”€β”€ App/
β”‚ β”‚ β”œβ”€β”€ Controllers/ // added
β”‚ β”‚ β”‚ └── UserController.swift // added
β”‚ β”‚ β”œβ”€β”€ Models/
β”‚ β”‚ β”œβ”€β”€ app.swift
β”‚ β”‚ β”œβ”€β”€ boot.swift
β”‚ β”‚ β”œβ”€β”€ configure.swift
β”‚ β”‚ └── routes.swift
β”‚ └── Run/
β”‚ └── main.swift
β”œβ”€β”€ Tests/
β”œβ”€β”€ Public/
β”œβ”€β”€ Dependencies/
└── Products/

I used the terminal. In projectName/ execute
> mkdir Sources/App/Controllers/
> 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.

So let’s move our get(β€œuser”) functionality into our Controller by writing the following code into UserController.swift:

import Vaporfinal class UserController {  func list(_ req: Request) throws -> Future<View> {
return User.query(on: req).all().flatMap { users in
let data = ["userlist": users]
return try req.view().render("userview", data)
}
}
}

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 -> Future<Something> {
...
}

Notice the Future<Something> as a return value. That Something depends on what you want to return. For example if you want to return a View just as we did you would write Future<View> as a return value. But if you want to return let’s say a list of users as JSON you would write Future<[User]>. And within your function you would just do: return User.query(on: req).all().

Moving on! How do we link a route like get(β€œuser”) in our routes.swift to use our list function? It is so super simple, in your routes.swift write:

import Vaporpublic func routes(_ router: Router) throws {  let userController = UserController()  // added
router.get("users", use: userController.list) // added
router.post("users") { req -> Future<Response> in
...
}
}

We deleted the former route get(β€œuser”) and the import Leaf here

We initiate our user controller and define that get(β€œuser”) shall use our user controllers list function.

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

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

For completeness sake let’s move post(β€œusers”) to our controller too 😊
In our UserController.swift:

import Vaporfinal class UserController {  func list(_ req: Request) throws -> Future<View> {
...
}
func create(_ req: Request) throws -> Future<Response> {
return try req.content.decode(User.self).flatMap { user in
return user.save(on: req).map { _ in
return req.redirect(to: "users")
}
}
}

}

And then finally in our routes.swift:

import Vaporpublic func routes(_ router: Router) throws {  let userController = UserController()
router.get("users", use: userController.list)
router.post("users", use: userController.create) // added
}

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

5. Where to go from here

You can find a list of all tutorials with example projects on Github here:
πŸ‘‰πŸ» https://github.com/vaporberlin/vaporschool

I am really happy you read my article! If you have any suggestions or improvements of any kind let me know! I’d love to hear from you! 😊

--

--

Martin Lasek
Martin Lasek

Written by Martin Lasek

I'm an always optimistic, open minded and knowledge seeking fullstack developer passionate about UI/UX and changing things for the better :)

No responses yet