How to create your first website using Vapor 4 and Leaf?

Let's build a web page in Swift. Learn how to use the brand new template engine of the most popular server side Swift framework.

Vapor

Project setup

Start a brand new project by using the Vapor toolbox. If you don't know what's the toolbox or how to install it, you should read my beginner's guide about Vapor 4 first. You can create a new project by running the following command.

vapor new MyWebsite

It will clone the starter template, which is a good starting point to build an API or a website.

So we just cloned the API template project, which doesn't include the Leaf templating engine by default. You have to add it as a dependency by hand, or you can start with the web template. Personally I don't like to start with the web template, since I'm always using Fluent, but it really doesn't matters, until you have these lines in your package definition.

// swift-tools-version:5.2
import PackageDescription

let package = Package(
    name: "examples",
    platforms: [
       .macOS(.v10_15)
    ],
    dependencies: [
        // ๐Ÿ’ง A server-side Swift web framework.
        .package(url: "https://github.com/vapor/vapor.git", from: "4.3.0"),
        .package(url: "https://github.com/vapor/leaf.git", from: "4.0.0-rc"),
        .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-rc"),
        .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0-rc")
    ],
    targets: [
        .target(name: "App", dependencies: [
            .product(name: "Leaf", package: "leaf"),
            .product(name: "Fluent", package: "fluent"),
            .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
            .product(name: "Vapor", package: "vapor")
        ]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)

Open the project by double clicking the Package.swift file. Xcode 11 will download all the required package dependencies first, then you'll be ready to run your app (you might have to select the Run target first in Xcode) and write some server side Swift code.

We'll start with our very first Leaf template. ๐Ÿƒ


Getting started with Leaf 4

Leaf is a powerful templating language with Swift-inspired syntax. You can use it to generate dynamic HTML pages for a front-end website or generate rich emails to send from an API.

If you choose a domain-specific language (DSL) for writing type-safe HTML (such as Plot) you'll have to rebuild your backend application to see the changes. Leaf is a dynamic template engine, this means that you can even add multiple themes to your website & change them on the fly without recompiling your Swift codebase. Let me show you how to setup Leaf.

import Vapor
import Leaf
import Fluent
import FluentSQLiteDriver

public func configure(_ app: Application) throws {

    // Serves files from `Public/` directory
    app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))

    // Configure Leaf
    app.views.use(.leaf)
    app.leaf.cache.isEnabled = app.environment.isRelease

    // Configure SQLite database
    app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)

    // Configure migrations
    app.migrations.add(CreateTodo())

    try routes(app)
}

With just a few lines of code you are ready to use Leaf. If you build & run your app you'll be able to modify your templates and see the changes instantly if reload your browser. If you build in release mode the Leaf cache will be enabled, so you need to restart your application if you alter a template.

Your templates should be placed under the Resources/Views directory by default. You can change this by setting a new root directory on the configuration property: app.leaf.configuration.rootDirectory.

Create a new file under the Resources/Views directory called index.leaf.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>#(title)</title>
  </head>
  <body>
    <h1>#(body)</h1>
  </body>
</html>

This file is our index HTML template. Leaf gives you the ability to put specific tags into your HTML code. These tags are always starting with the # symbol. You can think of these as preprocessor macros. The Leaf renderer will process the template file and replace the #() placeholders with actual values. I'll show you some more leaf tags later on. ๐Ÿ˜‰

You can enable basic syntax highlighting for .leaf files in Xcode by choosing the Editor โ–ธ Syntax Coloring โ–ธ HTML menu item. Unfortunately if you close Xcode you have to do this again and again for every single Leaf file.

After the template file has been processed it'll be rendered as a HTML string. Let me show you how this works in practice. First we need to respond some HTTP request, then we tell our template engine to render a template file, we send this as an HTML response automatically.

import Vapor
import Fluent
import Leaf

func routes(_ app: Application) throws {

    app.get { req in
        req.view.render("index", [
            "title": "Hi",
            "body": "Hello world!"
        ])
    }
}

The snippet above goes to your routes.swift file. Routing is all about responding to HTTP requests. In this example using the .get you can respond to the / path. In other words if you run the app and enter http://localhost:8080 into your browser, you should be able to see the rendered view as a response.

The first parameter of the render method is the name of the leaf file (without the extension). As a second parameter you can pass anything that conforms to the Encodable protocol. In this example we'll use a dictionary with the corresponding keys from the template file. ๐Ÿค“

If you run the app from Xcode, don't forget to set a custom working directory, or it won't work. You can also run the server from the command line by entering: swift run Run.

Congratulations! You just made your very first webpage. ๐ŸŽ‰


Working with Leaf 4

Leaf is a lightweight, but very powerful template engine. If you learn the basic principles, you'll be able to completely separate the view layer from the business logic. If you are familiar with HTML, you'll find that Leaf is easy to learn & use. I'll show you some handy tips real quick.

Extending templates

Splitting up templates is going to be essential if you are planning to build a multi-page website. You can create reusable components that you can export for later usage. In Leaf 3 template inheritance was working completely different. You can forget the the #embed & #set tags for good. ๐Ÿ’€

Now you can create base templates with importable content pieces for children. Child templates can extend the base functionality by defining exported content or string literals for the import placeholders. Don't worry, you'll understand it when you look at the code.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>#(title)</title>
    <meta name="description" content="#(description)">
  </head>
  <body>
    <main>
        #import("body")
    </main>
  </body>
</html>

Above is an example index.leaf template, which can be a really good starting point. We can render the title & description properties, since we're going to set them up from Swift, but we import a custom body content for later customization.

It's worth to mention that Leaf 4 has a refreshed body syntax. You don't use brackets anymore, but you have to provide a corresponding #end element.

A page.leaf template can inherit from the index template by exporting a custom body. You can see how to extend a base template below, by using the brand new Leaf 4 body syntax

#extend("index"):
    #export("body"):
        <h1>#(title)</p>
        <p>#(content)</p>
    #endexport
#endextend

It's time to render the page template. As you can see we don't have to deal with the body, since it's already defined in the extension / child template. We only have to pass around the title, description and content keys with proper values for the final rendering.

app.get { req in
    req.view.render("page", [
        "title": "My Page",
        "description": "This is my own page.",
        "content": "Welcome to my page!"
    ])
}

It's possible to have multiple level of inhertiance, so for example index โ–ธ page โ–ธ welcome. If you follow the same pattern from above you can create a nice hierarchy for your views. I believe that this approach is way better than the previous one was in Leaf 3, I really like this change. ๐Ÿ˜Š

Rendering views using custom context

Passing some custom data to the view is super easy, you just have to conform to the Encodable protocol. This protocol might sounds familiar, because the well-known Codable protocol is just a composition of the decodable & encodable protocols. Time to change some leaf files.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>#(index.title)</title>
    <meta name="description" content="#(index.description)">
  </head>
  <body>
    <main>
        #import("body")
    </main>
  </body>
</html>

Inside the index.leaf template, I prefixed the properties with the name of the template. This is because I want to build a pattern here by reflecting the view extension hierarchy in Swift as well.

#extend("index"):
    #export("body"):
        <h1>#(index.title)</h1>
        <p>#(page.content)</p>
    #endexport
#endextend

I prefixed the properties inside the page template too. As you can see inside the page.leaf file we can still access the properties of the base index template, so this means that we can reuse components from the parents. Sometimes this is extremely useful for SEO stuff. ๐Ÿ”

import Vapor
import Leaf

struct Index: Codable {
    var title: String
    var description: String
}

struct Page: Codable {
    var content: String
}

func routes(_ app: Application) throws {

    app.get { req -> EventLoopFuture<View> in
        struct Context: Codable {
            var index: Index
            var page: Page
        }

        let context = Context(index: .init(title: "My page", description: "This is my Page"),
                              page: .init(content: "Welcome to my page!"))
        
        return req.view.render("page", context)
    }
}

I made two new Codable structs for the two templates, one for the index and an other for the page. I'll compose them together by a third struct called Context. This third object can be declared at a local scope if you are not planning to reuse it anywhere else.

This pattern helps me a lot to have a sane view hierarchy. It's also possible to utilize a builder pattern to construct these contexts for static pages. I hope you get the point. ๐Ÿ˜…

Conditions

You can use Swift-like expressions to evaluate a range of conditions in Leaf. The following options are available: >, <, >=, <=, ==, !=, &&, && and many more.

#if(2 == 3):
    <b>Impossible, two is equal to three</b>
#elseif(2 != 4):
    <b>Two is not equal to four</b>
#else:
    <b>Broken math</b>
#endif

#if(page.title != nil):
    <p>Page title is `#(page.title)`</p>
#endif

Loops

If you pass around an array of Codable items in the context object you can iterate through them by using the #for tag. If the elements in the array are objects you can access them through the dot notation, otherwise you can simply refer to them by the given name from the for tag.

#for(item in items):
<div>
    #if(isFirst):
        <h1>List of items</h1>
    #endif
    <h2>#(item.title)</h2>
    <p>#(item.description)</p>
</div>
#endfor

Providing the context object is very simple:

import Vapor
import Leaf

struct Item: Codable {
    var title: String
    var description: String
}

func routes(_ app: Application) throws {

    app.get { req -> EventLoopFuture<View> in
        struct Context: Codable {
            var items: [Item]
        }

        let context = Context(items: [
            .init(title: "#01", description: "Description #01"),
            .init(title: "#02", description: "Description #02"),
            .init(title: "#03", description: "Description #03"),
        ])
        return req.view.render("items", context)
    }
}

You can check for specific conditions inside the loop such as isFirst. You can use nested arrays plus there are many more useful features, but the best thing is that if you can't find something, you can even extend Leaf by creating a custom tag. Version 4 was worth the wait, this template engine is better than ever and I'm very optimistic about the future of Leaf. ๐Ÿ˜Ž

Share this article on Twitter.
Thank you. ๐Ÿ™

Picture of Tibor Bรถdecs

Tibor Bรถdecs

Creator of https://theswiftdev.com (weekly Swift articles), server side Swift enthusiast, full-time dad. -- Follow me & feel free to say hi. ๐Ÿค˜๐Ÿป -- #iOSDev #SwiftLang

Twitter · GitHub


๐Ÿ“ฌ

100% Swift news, delivered right into your mailbox

Subscribe to my monthly newsletter. On the first Monday of every month, you'll get an update about the most important Swift community news, including my articles.