Tutorial: How to useΒ Leaf

Yeey time to learn leafβ€Šβ€”β€Ša templating language to easily generate views!

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

This tutorial is a natural follow-up of How to set up a Vapor 3 project. You can either go for that tutorial first and come back or just skip it and read on 😊


Index

1. Create a new project
2. Generate the Xcode project
3. Configure your project to use Leaf
4. Create your first template / view
5. Implement a route to serve the view
6. BONUS: Passing data to the view
7. Where to go from here


1. Create a newΒ project

Vapor toolbox is so nice it let’s you easily create new projects using any git-repo as a template. Since we will build upon the outcome of the aforementioned tutorial, we will create a new project using it as a template:

vapor new projectName --template=vaporberlin/my-first-route
NOTE: You can see what vapor toolbox is all capable of by typing vapor --help in your terminalΒ :)

Our command will create a project with the name projectName using my-first-route from vaporberlin as a template (thanks vapor toolbox 🀜🏻 πŸ€›πŸ» ).


2. Generate the XcodeΒ project

Before we generate an Xcode project we would have to add Leaf as a dependency and also change the package name within Package.swift:

// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "projectName", // changed
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
.package(url: "https://github.com/vapor/leaf.git", from: "3.0.0") // added
],
targets: [
.target(name: "App", dependencies: ["Vapor", "Leaf"]),
.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, generating the Xcode project and opening it for you. But when done you should have a project structure like this:

projectName/
β”œβ”€β”€ Package.swift
β”œβ”€β”€ Sources/
β”‚ β”œβ”€β”€ App/
β”‚ β”‚ β”œβ”€β”€ app.swift
β”‚ β”‚ β”œβ”€β”€ boot.swift
β”‚ β”‚ β”œβ”€β”€ configure.swift
β”‚ β”‚ └── routes.swift
β”‚ └── Run/
β”‚ └── main.swift
β”œβ”€β”€ Tests/
β”œβ”€β”€ Public/
β”œβ”€β”€ Dependencies/
└── Products/
If you see an error including β€œCNIOOpenSSL” when cmd+r you’re missing a dependency. Just run brew upgrade vapor and re-generate the project ✌🏻😊

3. Configure your project to useΒ Leaf

We will have to register the LeafProvider within our configure.swift:

import Vapor
import Leaf // added
public func configure(
_ config: inout Config,
_ env: inout Environment,
_ services: inout Services
) throws {
  // Register routes to the router
let router = EngineRouter.default()
try routes(router)
services.register(router, as: Router.self)
  let leafProvider = LeafProvider()    // added
try services.register(leafProvider) // added
  config.prefer(LeafRenderer.self, for: ViewRenderer.self)
}

And also to set the LeafRenderer as our preferred ViewRenderer πŸƒ
This will allow us to access the renderer from within our routes very easy.


4. Create your first template /Β view

Our view renderer leaf is expecting a directory called Resources/ and right underneath it a directory called Views/ to hold all view-files. So you will end up with a folder structure like this:

projectName/
β”œβ”€β”€ Package.swift
β”œβ”€β”€ Sources/
β”‚ β”œβ”€β”€ App/
β”‚ β”‚ β”œβ”€β”€ app.swift
β”‚ β”‚ β”œβ”€β”€ boot.swift
β”‚ β”‚ β”œβ”€β”€ configure.swift
β”‚ β”‚ └── routes.swift
β”‚ └── Run/
β”‚ └── main.swift
β”œβ”€β”€ Resources/
β”‚ └── Views/
β”œβ”€β”€ Tests/
β”œβ”€β”€ Public/
β”œβ”€β”€ Dependencies/
└── Products/
NOTE: They have to be directories (blue) and not groups (yellow). I created them in terminal with mkdir -p Resources/Views/

You may have to re-generate your Xcode project with vapor xcode -y in order to let Xcode see your new directories. When done in Resources/Views/ create a new file named welcome.leaf with following code:

<!DOCTYPE html>
<html>
<head>
<title>Leaf</title>
</head>
<body> <h1> Welcome to my first leaf template! </h1> </body>
</html>
NOTE: Select welcome.leaf and go to Editor>Syntax Coloring>HTML πŸ˜‰

5. Implement a route to serve theΒ view

In your routes.swift add a new route and call it.. hmmm.. πŸ€”

import Vapor
public func routes(_ router: Router) throws {
  router.get("name") { req in
return "Ethan Hunt"
}
  router.get("age") { req in
return 23
}
  router.get("json") { req in
return Person(name: "Martin J. Lasek", age: 26)
}
  router.get("view") { req -> Future<View> in
return try req.view().render("welcome")
}

}
struct Person: Content {
var name: String
var age: Int
}

The function req.view() creates and returns a ViewRenderer. And since we configured LeafRenderer as our ViewRenderer we can useΒ .leaf files πŸƒ!

If you now hit cmd + r or the play button on top of Xcode, it will start the application. The console in Xcode tells you where. For me it’s localhost:8080.

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

If you now fire up localhost:8080/view in your browser you will get a view served using Leaf as a templating language! πŸ™ŒπŸ»


6. BONUS: Passing data to theΒ view

It’s super easy to pass data into a view. And this is how it looks like:

import Vapor
public func routes(_ router: Router) throws {
  router.get("name") { req in
return "Ethan Hunt"
}
  router.get("age") { req in
return 23
}
  router.get("json") { req in
return Person(name: "Martin J. Lasek", age: 26)
}
  router.get("view") { req -> Future<View> in
return try req.view().render("welcome")
}
  router.get("bonus") { req -> Future<View> in
let data = ["name": "Ethan", "age": "26"]
return try req.view().render("whoami", data)
}

}
struct Person: Content {
var name: String
var age: Int
}

If you now create a new file in Resources/Views/ called whoami.leaf you can access the passed data in it like so:

<!DOCTYPE html>
<html>
<head>
<title>Leaf</title>
</head>
  <body>
<h1> You rule #(name)! Can't believe you're already #(age)!</h1>
</body>
</html>

If you have a class or struct that conforms to content your are also able to pass an instance of it into the view like so:

import Vapor
public func routes(_ router: Router) throws {
  router.get("name") { req in
return "Ethan Hunt"
}
  router.get("age") { req in
return 23
}
  router.get("json") { req in
return Person(name: "Martin J. Lasek", age: 26)
}
  router.get("view") { req -> Future<View> in
return try req.view().render("welcome")
}
  router.get("bonus") { req -> Future<View> in
let developer = Person(name: "Martin", age: 26)
return try req.view().render("whoami", developer)
}
}
struct Person: Content {
var name: String
var age: Int
}

Now re-run your application and fire up localhost:8080/bonus πŸš€Β 
That’s it! You successfully implemented a leaf template project πŸŽ‰Β !


7. 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! 😊