Close Menu
geekfence.comgeekfence.com
    What's Hot

    Customer experience management (CXM) predictions for 2026: How customers, enterprises, technology, and the provider landscape will evolve 

    December 28, 2025

    What to Know About the Cloud and Data Centers in 2026

    December 28, 2025

    Why Enterprise AI Scale Stalls

    December 28, 2025
    Facebook X (Twitter) Instagram
    • About Us
    • Contact Us
    Facebook Instagram
    geekfence.comgeekfence.com
    • Home
    • UK Tech News
    • AI
    • Big Data
    • Cyber Security
      • Cloud Computing
      • iOS Development
    • IoT
    • Mobile
    • Software
      • Software Development
      • Software Engineering
    • Technology
      • Green Technology
      • Nanotechnology
    • Telecom
    geekfence.comgeekfence.com
    Home»iOS Development»How to create your first website using Vapor 4 and Leaf?
    iOS Development

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

    AdminBy AdminDecember 6, 2025No Comments11 Mins Read0 Views
    Facebook Twitter Pinterest LinkedIn Telegram Tumblr Email
    How to create your first website using Vapor 4 and Leaf?
    Share
    Facebook Twitter LinkedIn Pinterest Email


    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.

    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.

    // swift-tools-version:5.3
    import PackageDescription
    
    let package = Package(
        name: "myProject",
        platforms: [
           .macOS(.v10_15)
        ],
        dependencies: [
            // 💧 A server-side Swift web framework.
            .package(url: " from: "4.32.0"),
            .package(url: " .exact("4.0.0-tau.1")),
            .package(url: " .exact("1.0.0-tau.1.1")),
        ],
        targets: [
            .target(name: "App", dependencies: [
                .product(name: "Leaf", package: "leaf"),
                .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 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 & the proper device) and write some server side Swift code.

    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 entire backend application if you want to change your templates. Leaf is a dynamic template engine, this means that you can change templates on the fly without recompiling your Swift codebase. Let me show you how to setup Leaf.

    import Vapor
    import Leaf
    
    public func configure(_ app: Application) throws {
    
        app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
    
        if !app.environment.isRelease {
            LeafRenderer.Option.caching = .bypass
        }
    
        app.views.use(.leaf)
    
        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, that’s because we’ve bypassed the cache mechanism using the LeafRenderer.Option.caching property. If you build your backend application in release mode the Leaf cache will be enabled, so you need to restart your server after you edit a template.

    Your templates should have a .leaf extension and they should be placed under the Resources/Views folder inside your working directory by default. You can change this behavior through the LeafEngine.rootDirectory configuration and you can also alter the default file extension with the help of the NIOLeafFiles source object.

    import Vapor
    import Leaf
        
    public func configure(_ app: Application) throws {
    
        app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
    
        if !app.environment.isRelease {
            LeafRenderer.Option.caching = .bypass
        }
        
        let detected = LeafEngine.rootDirectory ?? app.directory.viewsDirectory
        LeafEngine.rootDirectory = detected
    
        LeafEngine.sources = .singleSource(NIOLeafFiles(fileio: app.fileio,
                                                        limits: .default,
                                                        sandboxDirectory: detected,
                                                        viewDirectory: detected,
                                                        defaultExtension: "html"))
        
        app.views.use(.leaf)
    
        try routes(app)
    
    }
    

    The LeafEngine uses sources to look up template locations when you call your render function with a given template name. You can also use multiple locations or build your own lookup source if you implement the LeafSource protocol if needed.

    import Vapor
    import Leaf
        
    public func configure(_ app: Application) throws {
    
        app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
    
        if !app.environment.isRelease {
            LeafRenderer.Option.caching = .bypass
        }
        
        let detected = LeafEngine.rootDirectory ?? app.directory.viewsDirectory
        LeafEngine.rootDirectory = detected
    
        let defaultSource = NIOLeafFiles(fileio: app.fileio,
                                         limits: .default,
                                         sandboxDirectory: detected,
                                         viewDirectory: detected,
                                         defaultExtension: "leaf")
    
        let customSource = CustomSource()
    
        let multipleSources = LeafSources()
        try multipleSources.register(using: defaultSource)
        try multipleSources.register(source: "custom-source-key", using: customSource)
    
        LeafEngine.sources = multipleSources
        
        app.views.use(.leaf)
    
        try routes(app)
    }
    
    struct CustomSource: LeafSource {
    
        func file(template: String, escape: Bool, on eventLoop: EventLoop) -> EventLoopFuture {
            /// Your custom lookup method comes here...
            return eventLoop.future(error: LeafError(.noTemplateExists(template)))
        }
    }
    

    Anyway, this is a more advanced topic, we’re good to go with a single source, also I highly recommend using a .html extension instead of leaf, so Xcode can give us partial syntax highlight for our Leaf files. Now we are going to make our very first Leaf template file. 🍃

    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.

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

    
    
      
        
        
        #(title)
      
      
        
      
    
    

    Leaf gives you the ability to put specific building blocks into your HTML code. These blocks (or tags) are always starting with the # symbol. You can think of these as preprocessor macros (if you are familiar with those). The Leaf renderer will process the template file and print the #() placeholders with actual values. In this case both the body and the title key is a placeholder for a context variable. We’re going to set these up using Swift. 😉

    After the template file has been processed it’ll be rendered as a HTML output string. Let me show you how this works in practice. First we need to respond some HTTP request, we can use a router to register a handler function, then we tell our template engine to render a template file, we send this rendered HTML string with the appropriate Content-Type HTTP header value as a response, all of this happens under the hood automatically, we just need to write a few lines of Swift code.

    import Vapor
    import Leaf
    
    func routes(_ app: Application) throws {
    
        app.get { req in
            req.leaf.render(template: "index", context: [
                "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 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 template file (without the file extension). As a second parameter you can pass anything that can represent a context variable. This is usually in a key-value format, and you can use almost every native Swift type including arrays and dictionaries. 🤓

    When you run the app using Xcode, don’t forget to set a custom working directory, otherwise Leaf won’t find your templates. You can also run the server using the command line: swift run Run.

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

    Congratulations! You just made your very first webpage. 🎉

    Inlining, evaluation and block definitions

    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.

    Splitting up templates is going to be essential if you are planning to build a multi-page website. You can create reusable leaf templates as components that you can inline later on.

    We are going to update our index template and give an opportunity for other templates to set a custom title & description variable and define a bodyBlock that we can evaluate (or call) inside the index template. Don’t worry, you’ll understand this entire thing when you look at the final code.

    
    
      
        
        
        #(title)
        
      
      
        
    #bodyBlock()

    The example above is a really good starting point. We could render the index template and pass the title & description properties using Swift, of course the bodyBlock would be still missing, but let me show you how can we define that using a different Leaf file called home.html.

    #let(description = "This is the description of our home page.")
    #define(bodyBlock):
    

    #(header)

    #(message)

    #enddefine #inline("index")

    Our home template starts with a constant declaration using the #let syntax (you can also use #var to define variables), then in the next line we build a new reusable block with a multi-line content. Inside the body we can also print out variables combined with HTML code, every single context variable is also available inside definition blocks. In the very last line we tell the system that it should inline the contents of our index template. This means that we’re literally copy & paste the contents of that file here. Think of it like this:

    #let(description = "This is the description of our home page.")
    #define(bodyBlock):
    

    #(header)

    #(message)

    #enddefine #(title)
    #bodyBlock()

    As you can see we still need values for the title, header and message variables. We don’t have to deal with the bodyBlock anymore, the renderer will evaluate that block and simply replace the contents of the block with the defined body, this is how you can imagine the template before the variable replacement:

    #let(description = "This is the description of our home page.")
    
    
      
        
        
        #(title)
        
      
      
        

    #(header)

    #(message)

    Now that’s not the most accurate representation of how the LeafRenderer works, but I hope that it’ll help you to understand this whole define / evaluate syntax thing.

    You can also use the #evaluate tag instead of calling the block (bodyBlock() vs #evaluate(bodyBlock), these two snippets are essentially the same).

    It’s time to render the page template. Again, we don’t have to deal with the bodyBlock, since it’s already defined in the home template, the description value also exists, because we created a new constant using the #let tag. We only have to pass around the title, header and message keys with proper values as context variables for the renderer.

    app.get { req in
        req.leaf.render(template: "home", context: [
            "title": "My Page",
            "header": "This is my own page.",
            "message": "Welcome to my page!"
        ])
    }
    

    It’s possible to inline multiple Leaf files, so for example you can create a hierarchy of templates such as: index ▸ page ▸ welcome, just follow the same pattern that I introduced above. Worth to mention that you can inline files as raw files (#inline("my-file", as: raw)), but this way they won’t be processed during rendering. 😊

    LeafData, loops and conditions

    Passing some custom data to the view is not that hard, you just have to conform to the LeafDataRepresentable protocol. Let’s build a new list.html template first, so I can show you a few other practical things as well.

    #let(title = "My custom list")
    #let(description = "This is the description of our list page.")
    #var(heading = nil)
    #define(bodyBlock):
    
    
      #for(todo in todos):
    • #if(todo.isCompleted):✅#else:❌#endif #(todo.name)
    • #endfor
    #enddefine #inline("index")

    We declare two constants so we don’t have to pass around the title and description using the same keys as context variables. Next we use the variable syntax to override our heading and set it to a nil value, we’re doing this so I can show you that we can use the coalescing (??) operator to chain optional values. Next we use the #for block to iterate through our list. The todos variable will be a context variable that we setup using Swift later on. We can also use conditions to check values or expressions, the syntax is pretty much straightforward.

    Now we just have to create a data structure to represent our Todo items.

    import Vapor
    import Leaf
    
    struct Todo {
        let name: String
        let isCompleted: Bool
    }
    
    extension Todo: LeafDataRepresentable {
    
        var leafData: LeafData {
            .dictionary([
                "name": name,
                "isCompleted": isCompleted,
            ])
        }
    }
    

    I made a new Todo struct and extended it so it can be used as a LeafData value during the template rendering process. You can extend Fluent models just like this, usually you will have to return a LeafData.dictionary type with your object properties as specific values under given keys. You can extend the dictionary with computed properties, but this is a great way to hide sensitive data from the views. Just completely ignore the password fields. 😅

    Time to render a list of todos, this is one possible approach:

    func routes(_ app: Application) throws {
    
        app.get { req -> EventLoopFuture in
            let todos = [
                Todo(name: "Update Leaf 4 articles", isCompleted: true),
                Todo(name: "Write a brand new article", isCompleted: false),
                Todo(name: "Fix a bug", isCompleted: true),
                Todo(name: "Have fun", isCompleted: true),
                Todo(name: "Sleep more", isCompleted: false),
            ]
            return req.leaf.render(template: "list", context: [
                "heading": "Lorem ipsum",
                "todos": .array(todos),
            ])
        }
    }
    

    The only difference is that we have to be more explicit about types. This means that we have to tell the Swift compiler that the request handler function returns a generic EventLoopFuture object with an associated View type. The Leaf renderer works asynchronously so that’s why we have to work with a future value here. If you don’t how how they work, please read about them, futures and promises are quite essential building blocks in Vapor.

    The very last thing I want to talk about is the context argument. We return a [String: LeafData] type, that’s why we have to put an additional .array initializer around the todos variable so the renderer will know the exact type here. Now if you run the app you should be able to see our todos.

    Summary

    I hope that this tutorial will help you to build better templates using Leaf. If you understand the basic building blocks, such as inlines, definitions and evaluations, it’s going to be really easy to compose your template hierarchies. If you want to learn more about Leaf or Vapor you should check for more tutorials in the articles section or you can purchase my Practical Server Side Swift book.



    Source link

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email

    Related Posts

    SwiftText | Cocoanetics

    December 28, 2025

    Uniquely identifying views – The.Swift.Dev.

    December 27, 2025

    Unable to upload my app with Transporter. Some kind of version mismatch? [duplicate]

    December 26, 2025

    Experimenting with Live Activities – Ole Begemann

    December 25, 2025

    Announcing Mastering SwiftUI for iOS 18 and Xcode 16

    December 24, 2025

    Grouping Liquid Glass components using glassEffectUnion on iOS 26 – Donny Wals

    December 22, 2025
    Top Posts

    Understanding U-Net Architecture in Deep Learning

    November 25, 20258 Views

    Microsoft 365 Copilot now enables you to build apps and workflows

    October 29, 20258 Views

    Here’s the latest company planning for gene-edited babies

    November 2, 20257 Views
    Don't Miss

    Customer experience management (CXM) predictions for 2026: How customers, enterprises, technology, and the provider landscape will evolve 

    December 28, 2025

    After laying out our bold CXM predictions for 2025 and then assessing how those bets played out…

    What to Know About the Cloud and Data Centers in 2026

    December 28, 2025

    Why Enterprise AI Scale Stalls

    December 28, 2025

    New serverless customization in Amazon SageMaker AI accelerates model fine-tuning

    December 28, 2025
    Stay In Touch
    • Facebook
    • Instagram
    About Us

    At GeekFence, we are a team of tech-enthusiasts, industry watchers and content creators who believe that technology isn’t just about gadgets—it’s about how innovation transforms our lives, work and society. We’ve come together to build a place where readers, thinkers and industry insiders can converge to explore what’s next in tech.

    Our Picks

    Customer experience management (CXM) predictions for 2026: How customers, enterprises, technology, and the provider landscape will evolve 

    December 28, 2025

    What to Know About the Cloud and Data Centers in 2026

    December 28, 2025

    Subscribe to Updates

    Please enable JavaScript in your browser to complete this form.
    Loading
    • About Us
    • Contact Us
    • Disclaimer
    • Privacy Policy
    • Terms and Conditions
    © 2025 Geekfence.All Rigt Reserved.

    Type above and press Enter to search. Press Esc to cancel.