Kitura Stencil Tutorial: How to make Websites with Swift

Build a web-based frontend for your Kitura API using Stencil, in this server-side Swift tutorial!

Version

  • Swift 4.2, macOS 10.14, Xcode 10

It’s a lot of fun building up a back-end API in Swift with Kitura, and testing it with the OpenAPI Explorer, or maybe you’ve gone the distance to build an iOS app to consume your API. It’s totally understandable to want to show this work off to your friends.

However, what if they try to log in as “Leia Organa” through the API Explorer on your machine when you deploy to localhost, and they get annoyed at you for continually asking them to come over? Worse yet… what if they don’t have iPhones?!

In this tutorial on building websites with a Swift templating library named Stencil, you’ll use a pre-built back-end Kitura app for saving emoji info named EmojiJournal in order to:

  • Discover how a web-based front-end is structured.
  • Learn how Stencil templating works.
  • Set up your basic /client route for accessing the front-end on the web.

By the end of this tutorial, you’ll have a working web client that still needs some work, but is ready to go out of the box!

Note: This tutorial assumes you have some experience with using Kitura to build Swift web apps. See Kitura Tutorial: Getting Started With Server-Side Swift if you’re new to Kitura.

You’ll also need the following available to you in order to follow along:

  • MacOS 10.14 or higher
  • Xcode 10.1 or newer
  • Homebrew
  • PostgreSQL
  • Basic familiarity with Terminal, as you’ll use the command line quite a bit

Getting Started

To start, download the starter project materials using the Download Materials button at the top or bottom of this page.

This tutorial is going to contain some work in the following languages:

  • HTML (HyperText Markup Language)
  • JS (JungleScribble… kidding, JavaScript)

Regardless of your experience working with any of these, I humbly ask you to not be alarmed! I might not go into the deepest depths of explaining everything we write in other languages, but I will include a nugget or two of knowledge about everything you add to your project.

Open the starter project in Finder, and navigate to the EmojiJournalServer/public directory. Open the folder and look at the structure. It should look like so:

Web folder structure

This project will not require you to modify these files in any way, so you can largely leave these alone, but you should at least know what is in each directory:

  • css: This is an acronym for Cascading Style Sheets. Using CSS is how the different HTML components you will be creating will order and position themselves by reference. You have CSS files for both the emoji-picker component and the main page you will format.
  • img: This is short for images. This is nothing more than a folder for static image assets that will go on your web client. I am by no means a web designer, so if you see fit to make changes, go nuts!
  • js: The scripts that make your web client run as smoothly as it will. If you edit these, to quote npm (Node Package Manager): I hope you know what you’re doing.

It is good and common practice to load these into a folder called public on your front end. Typically, you could also put your .html files into their own directory here as well. However, you’re going to be working with Stencil, which is a powerful templating library written for Swift, and mostly maintained by Kyle Fuller.

Wait… Templating?

When I say templating, I mean the ability to reproduce multiple variables and components in a repeatable manner that you have the power to format.

Take this for example: Let’s say you wanted to write a webpage that always tells you the current month. A naïve approach to such a task might look like so in HTML:

<!DOCTYPE html>
<html>
  <head>
    <title>The current month is December!</title>
  </head>
</html>

Depending on when you are reading this tutorial, this result might be appropriate, but there’s only approximately an 8.5% chance it is.

That is hardly sustainable; I wouldn’t want to be the web developer responsible for maintaining this! Thanks to Stencil, you now have the ability to pass in a variable, and Stencil will render your template file to spit out the proper date! Your .stencil file would look like so:

<!DOCTYPE html>
<html>
  <head>
    <title>The current month is {{ currentMonth }}!</title>
  </head>
</html>

The main difference here is the inclusion of the {{ currentMonth }}, but notice the curly braces around each end of the variable. Those are the delimiter that Stencil will search for when looking for a place to insert information. Stencil essentially operates on this workflow:

  1. Prepare data from your API.
  2. Set up a context: a dictionary of key/value pairs.
  3. Populate your context with the values you want to display.
  4. Render a template in your response, using a context.

If you didn’t raise an eyebrow halfway through this list, go back and look at #2. Hopefully, you’re thinking, “Wait a minute… there’s all this focus in Swift on the Codable protocol, and now I have to form a key-value pair dictionary? What gives, David?!” You should know I would never pull one over on you like that — of course you can use Codable!

On the Swift side of things, you need to prepare an object that is either Codable or an array of homogenous Codable objects. You’ll cover the whole flow from front to back shortly, but if you wanted to reduce the above sample to a short code snippet, it might look like this:

struct CurrentMonth: Codable {
  var month: String
}

try response.render("home.stencil", 
  with: CurrentMonth(month: "December"), forKey: "currentMonth")

And, then, in your home.stencil file, you would handle this variable like so:

<!DOCTYPE html>
<html>
  <head>
    <title>
      The current month is {{ currentMonth.month }}!
    </title>
  </head>
</html>

Notice the slight change in the above example. This is because your Codable object has a property on it titled month. Since Codable handles serializing the JSON for you, Stencil gives you an easy way to read stored properties in your .stencil file.

Note: Just like most of Kitura’s functionality, if you want to load a raw dictionary of values and keys, you can. Version 1.x APIs still work.

Loops and Other Operations in Stencil

Just like control flow in most modern programming languages, Stencil has a way to handle looping through an array of objects that you may want to utilize. Using our previous example, say you render an array into your response like so:

let birthdays = [CurrentMonth(month: "September"), 
                 CurrentMonth(month: "January"),
                 CurrentMonth(month: "March")]
                 
try response.render("home.stencil", with: birthdays, 
  forKey: "birthdays")

This means you now have a collection of objects available to you in Stencil rather than just a singular object.

Now, you need to make use of one of Stencil’s built in template tags with a for loop. Your HTML might look like this:

<html>
  <body>
  {% for birthday in birthdays %}
    <li> Month: {{ birthday.month }} </li>
  {% endfor %}
  </body>
</html>

Notice the difference between the {% and the {{ delimiters. As you have already learned, anything inside {{ }} is going to represent as a string inside your rendered HTML document. However, if something is inside the {% %} delimiters, then this is going to apply to a tag that Stencil recognizes. Many Stencil tags require a delimited tag like endfor that “ends” the previous command.

The for loop is certainly an example of that, but you can also do similar with an if statement in Stencil like so:

{% if birthday.month %}
  <li> Month: {{ birthday.month }}</li>
{% else %}
  <li> No month listed. </li>
{% endif %}

Here’s a partial list of some of the built in tags that Stencil has available for you to use:

  • for
  • if
  • ifnot
  • now
  • filter
  • include
  • extends
  • block

If you want to check the documentation on how you can use any of these tags in your own website template, visit Kyle’s documentation website at https://stencil.fuller.li/en/latest/builtins.html.

Note: Of the tags you can use, the include tag is of particular interest, as you can pass a context through to another .stencil file in your original file by typing {% include "secondTemplate.stencil" %}. You won’t use it in this project, but some websites can become a bit cumbersome if you don’t split them up — this can be helpful!

With this theory behind you, it’s finally time to start updating your starter project!

Setting up PostgreSQL

In order to build the sample project, you’ll need to have PostgreSQL installed on your development system, which you can do using Homebrew.

Run the following command in Terminal to install PostgreSQL, if it’s not already installed:

brew install postgres

Then start the database server and create a database for the EmojiJournal app using these commands:

pg_ctl -D /usr/local/var/postgres start
initdb /usr/local/var/postgres
sudo -u postgres -i
createdb emojijournal

Note: You may also have some luck running PostgreSQL in a Docker container.

Adding Stencil to Your Project

Open up EmojiJournalServer/Package.swift in a text editor.

First, add the Stencil dependency at the bottom of your list of dependencies:

.package(url: 
"https://github.com/IBM-Swift/Kitura-StencilTemplateEngine.git", 
.upToNextMinor(from: "1.11.0")),

Make sure the previous line has a , at the end, or your Package.swift won’t compile.

Next, scroll down to the Application target, and in the list of dependencies this target has, add KituraStencil to the end of the array. It should look like this:

.target(name: "Application", dependencies: [ "Kitura", "CloudEnvironment", "SwiftMetrics", "Health", "KituraOpenAPI", "SwiftKueryPostgreSQL", "SwiftKueryORM", "CredentialsHTTP", "KituraStencil"]),

Save your file and navigate to the root directory of your project in Terminal.

This is a good time to remind you that the command swift package generate-xcodeproj command will resolve your dependency graph for you, and then generate an Xcode project for you. And remember that Swift Package Manager runs swift package update and swift build under the hood for you!

Run the command swift package generate-xcodeproj from the EmojiJournalServer folder and when it’s done, open EmojiJournalServer.xcodeproj. Build your project in Xcode, and make sure everything runs OK.

Web Client Routing

The starter project contains a router for JournalEntry objects and for UserAuth management. You’re going to add another route for managing connecting to your web client.

In Xcode, navigate to the Sources/Application/Routes folder. Create a new file, and name it WebClientRoutes.swift.

At the top of this file, import the following libraries:

import Foundation
import LoggerAPI
import KituraStencil
import Kitura
import SwiftKueryORM

Now, add a function that will help you register a route on your main Router object to handle the web client:

func initializeWebClientRoutes(app: App) {

  // 1
  app.router.setDefault(templateEngine: StencilTemplateEngine())
  
  // 2
  app.router.all(middleware: StaticFileServer(path: "./public"))
  
  // 3
  app.router.get("/client", handler: showClient)
  
  // 4
  Log.info("Web client routes created")
}

Look at each line of this function you’ve just added:

  1. Since you’ve added the Stencil dependency to your project, you need to tell Kitura that Stencil is going to be the format for templating your HTML. Yes — you do have a choice when it comes to other templating engines, but our team has chosen Stencil!
  2. Here, you need to tell Kitura that, when searching for static files to serve up (images, etc.) which directory to look in, and this tells Kitura to look in your aptly named public directory.
  3. Here, you are registering the /client route on your router, and you’ll handle this route with Stencil and Kitura shortly.
  4. Log, log, log your work!

Beneath this function, add the following function signature:

func showClient(request: RouterRequest, 
  response: RouterResponse, next: @escaping () -> Void) {

}

This way, your project can compile, and you’ve now specified a function to handle your route. From here on out, you’re going to write a bunch of Swift code that serves up what you will eventually shape in a Stencil file.

Start by declare the following object above your initializeWebClientRoutes function:

struct JournalEntryWeb: Codable {
  var id: String?
  var emoji: String
  var date: Date
  var displayDate: String
  var displayTime: String
  var backgroundColorCode: String
  var user: String
}

This might look redundant at first; technically, it is. However, remember our earlier note about how Stencil can only serve stored properties? Stencil cannot handle computed properties of some objects. Notice that this object conforms to Codable, too!

Now, you might be thinking: “Wait… my JournalEntry object doesn’t have any computed properties!” Take a deep breath; it doesn’t. However, you’re going to extend it here so that it does for the sake of convenience.

Scroll up to the top of this file, but just underneath your imports, and add the following three computed properties to a fileprivate extension of your object:

fileprivate extension UserJournalEntry {
  var displayDate: String {
    get {
      let formatter = DateFormatter()
      formatter.dateStyle = .long
      return formatter.string(from: self.date)
    }
  }
  
  var displayTime: String {
    get {
      let formatter = DateFormatter()
      formatter.timeStyle = .long
      return formatter.string(from: self.date)
    }
  }
  
  var backgroundColorCode: String {
    get {
      guard let substring = id?.suffix(6).uppercased() else {
        return "000000"
      }
      return substring
    }
  }
}

A couple of points about what you’ve just added:

  • You want to make sure you are extending UserJournalEntry, which will include the user in every object you pass through to your Stencil context.
  • displayDate and displayTime are purely for convenience. You could absolutely create these variables from the date property you pass to your HTML page, but this allows you to do it with Swift and make your HTML a bit simpler!
  • The backgroundColorCode is a design decision made in lockstep with your iOS app, based on the ID of a journal entry object. Hey, it works!

Alright, now you’ve got the object that you’re going to pass into the context.

Add the following code to your showClient route handler:

UserJournalEntry.findAll(using: Database.default) { 
  entries, error in
  
  guard let entries = entries else {
    response.status(.serviceUnavailable).send(json: ["Message": 
      "Service unavailable:" +
      "\(String(describing: error?.localizedDescription))"])
    return
  }
  
  let sortedEntries = entries.sorted(by: {
    $0.date.timeIntervalSince1970 > 
    $1.date.timeIntervalSince1970
  })
}

Notice that you are making use of the ORM function on UserJournalEntry instead of just JournalEntry. This is to override the user authentication on the server side — temporarily, of course! Later on you’ll need to handle authentication properly. By guarding against a potential issue with the database, you make sure that you protect your web client against unexpected issues.

After you get a handle on your array of entries, then you sort them so they are date-descending.

Next, you’re going to render the final array of objects that you’ll send to your .stencil file. Inside the closure for your UserJournalEntry.findAll function, but at the very bottom, add the following code:

//1
var webEntries = [JournalEntryWeb]()

for entry in sortedEntries {

  // 2
  webEntries.append(JournalEntryWeb(id: entry.id, 
    emoji: entry.emoji, date: entry.date, 
    displayDate: entry.displayDate, 
    displayTime: entry.displayTime, 
    backgroundColorCode: entry.backgroundColorCode, 
    user: entry.user))
}

// 3
do {
  try response.render("home.stencil", with: webEntries, 
    forKey: "entries")
} catch let error {
  response.status(.internalServerError)
    .send(error.localizedDescription)
}

With this code, you:

  1. Create a buffer array of JournalEntryWeb objects to send over.
  2. Populate it using a combination of the computed properties from your extension in this file and the stored properties this object already carries.
  3. Stuff your response.render command into a do-catch block, and you’re scot free!

Lastly, in Xcode, open Sources/Application/Application.swift and go to postInit. Right beneath where you call initializeUserRoutes, add the following function:

initializeWebClientRoutes(app: self)

Nice! Now everything is ready to go. Go back to WebClientRoutes.swift, set a breakpoint inside showClient.

Build and run your server in Xcode.

Open up a web browser, and visit http://localhost:8080/client. Your breakpoint should trigger; step through the inherent functionality and watch your context build! After you let your breakpoint go and let the route handler finish, check your browser and…

Initial web client

To test out your web UI, you should add a couple of journal entries if you don’t have any already. You can use the OpenAPI Spec with your Kitura app to do so, by visiting http://localhost:8080/openapi/ui. For instructions on using OpenAPI, see The OpenAPI Spec and Kitura: Getting Started. You’ll need to create a user and then use the /entries POST command to add an entry into your database.

Adding Stencil code

From here on out, you’re going to be editing your home.stencil file and checking the output to see what happens.

In Xcode, go to the Views directory, and open home.stencil. Three things to note about this file as you continue.

  1. This technically counts as a static file. This means that you can edit this file, save it, and not have to re-run your server to notice the changes! This also means you should build and run your server, and not worry about restarting it for the rest of this tutorial.
  2. Xcode is… less than desirable as a non-Swift/Objective-C text editor. To make it marginally better, go to the top menu bar, and select Editor ▸ Syntax Coloring ▸ HTML. I won’t blame you if you kick the tires on Visual Studio Code or Sublime Text.
  3. Take a moment to read through the rest of the code, and peek at the accompanying style in index.css if you want. The web client will use something that looks like a UICollectionView with cards to sort all of the entries.

Ready to edit the template file?

Scroll to line 27, which should read like </article>, and after this line, add the following code snippet:

{% for entry in entries %}
<article class="card" 
  style="background-color: #{{ entry.backgroundColorCode }};">
<div class="top-content-box">
  <div class="emoji-date">
    <p>{{ entry.displayDate }}<br>{{ entry.displayTime }}</p>
  </div>
  <input id={{ entry.id }} class="delete-button" type="submit" 
    value="✕" onClick="deleteEntry(this.id)" 
    onEntry="hideEmojiPicker()">
</div>
<p class="emoji-text">{{ entry.emoji }}</p>
</article>
{% endfor %}

Here’s what you’ve just done:

  • You create an HTML element called an article, which allows you to classify an element with sub-sections called divs. One of the nice things about HTML is that you can override your CSS file with specific style instructions inline; you do that here with the hex code from backgroundColorCode passed through from your context from Stencil!
  • Furthermore, you make nice and immediate use of the two computed properties for the date and time.
  • You set up an input element at the top right-hand corner to delete an entry (Mac vs. Windows holy war participants, don’t @ me), but you may notice that you haven’t yet written the function to handle this yet — that’s OK!
  • The main course, obviously, is the particular emoji that you are displaying in the card!
  • And all of this wrapped in a for loop across entries!

Think about this for a second; you just wrote a way to display however many EmojiJournal entries with one block of HTML, and you did it all with Swift and a little bit of sugar from Stencil!

Reload your browser. Regardless of what user you submitted them under, if you’ve loaded a couple of journal entries to your database, your website should show them:

Completed template

Nice work! Have you ever wanted to slap your Ray-Bans on, cross your arms, and tell Twitter that you’re a web developer? Spin up your internet machine — you can now!

Where to Go From Here?

You can download the finished project for this tutorial using the Download Materials button at the top or bottom of this page.

Congratulations! You have a working web client now!

The web version of EmojiJournal is not quite yet a fully-featured app, since you’ve not implemented add functionality in the web interface. But you’re well on your way using Stencil with Kitura. See our book Server Side Swift with Kitura for more information on finishing off the web version of EmojiJournal.

Stencil is a very powerful framework for templating, and you should give it a shot on its own. You can check out the GitHub repo for it at https://github.com/stencilproject/Stencil.

If you have any questions or comments on this tutorial, please leave them in the comments

Add a rating for this content

Contributors

Comments