Templating Vapor Applications with Leaf
Use Leaf, Vapor’s templating engine, to build a front-end website to consume your server-side Swift API! By Tim Condon.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Templating Vapor Applications with Leaf
20 mins
Creating APIs in Vapor and building out an iOS app as the front end may be something you’re fairly familiar with, but you can create nearly any type of client to consume your API. In this tutorial, you’ll create a website-based client against your Vapor API. You’ll use Leaf, Vapor’s templating language, to create dynamic websites in Vapor applications.
You’ll use a Vapor app named TIL (Today I Learned) that hosts acronyms entered by users.
Note: This tutorial assumes you have some experience with using Vapor to build web apps.
See Getting Started with Server-side Swift with Vapor 4 if you’re new to Vapor. You’ll need to at least use the steps in that tutorial to install the Vapor Toolbox in order to follow along with this tutorial.
You’ll also need some familiarity with Docker (and have it installed). If you need to refresh your Docker knowledge, see Docker on macOS: Getting Started.
Note: This tutorial assumes you have some experience with using Vapor to build web apps.
See Getting Started with Server-side Swift with Vapor 4 if you’re new to Vapor. You’ll need to at least use the steps in that tutorial to install the Vapor Toolbox in order to follow along with this tutorial.
You’ll also need some familiarity with Docker (and have it installed). If you need to refresh your Docker knowledge, see Docker on macOS: Getting Started.
What is Leaf?
Leaf is Vapor’s templating language. A templating language allows you to pass information to a page so it can generate the final HTML server-side without knowing everything up front.
For example, in the TIL application, you don’t know every acronym that users will create when you deploy your application. Templating allows you handle this with ease.
Templating languages also allow you to reduce duplication in your webpages. Instead of multiple pages for acronyms, you create a single template and set the properties specific to displaying a particular acronym. If you decide to change the way you display an acronym, you only need to make the change to your code once for all pages to show the new format.
Finally, templating languages allow you to embed templates into other templates. For example, if you have navigation on your website, you can create a single template that generates the code for your navigation so that all templates that need navigation don’t duplicate code.
Getting Started
Download the starter project for this tutorial using the “Download Materials” button at the top or bottom of this page.
To use Leaf, you need to add it to your project as a dependency.
Using the starter project from this tutorial, open Package.swift.
Update Package.swift so that:
-
The
TILApp
package depends on theLeaf
package -
The
App
target depends on theLeaf
target to ensure it links properly
Your Package.swift should look like the following:
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "TILApp",
platforms: [
.macOS(.v10_15)
],
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-postgres-driver.git",
from: "2.0.0"),
// Leaf package dependency
.package(url: "https://github.com/vapor/leaf.git", from: "4.0.0")
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(
name: "FluentPostgresDriver",
package: "fluent-postgres-driver"),
.product(name: "Vapor", package: "vapor"),
// Leaf target dependency
.product(name: "Leaf", package: "leaf")
],
swiftSettings: [
.unsafeFlags(
["-cross-module-optimization"],
.when(configuration: .release))
]
),
.target(name: "Run", dependencies: [.target(name: "App")]),
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
By default, Leaf expects templates to be in the Resources/Views directory.
In Terminal, type the following to create these directories:
mkdir -p Resources/Views
Rendering a Page
In Xcode, create a new Swift file named WebsiteController.swift in Sources/App/Controllers.
This controller will hold all the website routes, such as one that will return a template that contains an index of all acronyms.
Open WebsiteController.swift and replace its contents with the following:
import Vapor
import Leaf
// 1
struct WebsiteController: RouteCollection {
// 2
func boot(routes: RoutesBuilder) throws {
// 3
routes.get(use: indexHandler)
}
// 4
func indexHandler(_ req: Request) -> EventLoopFuture<View> {
// 5
return req.view.render("index")
}
}
Here’s what this does:
-
Declare a new
WebsiteController
type that conforms toRouteCollection
. -
Implement
boot(routes:)
as required byRouteCollection
. -
Register
indexHandler(_:)
to process GET requests to the router’s root path, i.e., a request to /. -
Implement
indexHandler(_:)
that returnsEventLoopFuture<View>
. -
Render the index template and return the result. You’ll learn about
req.view
in a moment.
Leaf generates a page from a template called index.leaf inside the Resources/Views directory.
Note that the file extension’s not required by the render(_:)
call.
Create Resources/Views/index.leaf and insert the following:
<!DOCTYPE html>
<!-- 1 -->
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- 2 -->
<title>Hello World</title>
</head>
<body>
<!-- 3 -->
<h1>Hello World</h1>
</body>
</html>
Here’s what this file does:
-
Declare a basic HTML 5 page with a
<head>
and<body>
. - Set the page title to Hello World — this is the title displayed in a browser’s tab.
-
Set the body to be a single
<h1>
title that says Hello World.
You must register your new WebsiteController
.
Open routes.swift and in route(_:)
replace the code
app.get { req in
return "It works!"
}
with the following:
let websiteController = WebsiteController()
try app.register(collection: websiteController)
Vapor now uses WebsiteController
to handle the root /
route.
Configuring Leaf
Next, you must tell Vapor to use Leaf.
Open configure.swift and add the following to the imports section below import Vapor
:
import Leaf
Using the generic req.view
to obtain the view renderer allows you to switch to different templating engines easily.
While this may not be useful when running your application, it’s extremely useful for testing. For example, it allows you to use a test renderer to produce plain text, rather than having to parse HTML, in your test cases.
req.view
asks Vapor to provide a type that conforms to ViewRenderer
.
Vapor only provides PlaintextRenderer
, but LeafKit — the module Leaf is built upon — provides LeafRenderer
.
In configure.swift, add the following after try app.autoMigrate().wait()
:
app.views.use(.leaf)
This tells Vapor to use Leaf when rendering views and LeafRenderer
when asked for a ViewRenderer
type.