Why you don’t need a templating language like Pug or Handlebars in Dart

Ryan Knell
3 min readMay 25, 2021

I wanted to show a strategy to use Dart — and only Dart — in place of what I would have traditionally used PugJS or Handlebars to do.

I realised Dart already came build in with what most templating languages were offering, which is injecting data into a string and looping over data sets. This was a pain to do in JS because up until recently you couldn’t easily inject variables into strings without concatenating them. You can now with backticks, the equivalent in dart is a triple quote. ie “””

Another great feature of Android studio is it will detect HTML in Dart strings and provide coding assistance. Not sure if this is available in VSCode, but if not its worth a look.

We are going to build a basic site and then serve it locally using a quick Alfred server.

So if you want to render an easy to maintain HTML based site in Dart, read on. The full source code is available at the end of the article.

Step 1. Create the template.dart file

Create /views/template.dartand fill it with this:

String template(String title, String content) {
return """<html>
<head>
<title>$title</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body>
<div class='container'>
${_navbar()}
$content
<div>Footer!</div>

</div>
</body>
</html>
""";
}

String _navbar() => """
<!-- Image and text -->
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" href="#">
<img src="https://raw.githubusercontent.com/kevmoo/dart_side/master/Dash%20Dart%20PNG%20%20-%20white.png" width='200' class="d-inline-block align-top" alt="">
Great subreddits
</a>
</nav>""";

What you have here is a simple HTML site. A function called template that takes a String with content, and a String with a title.

You may also notice that there is a modular snippet in _navbar() which renders the navbar — it's much easier to reason about the code when you break up the components.

Now lets render some content.

Step 2. Create the list view

Create a file called /views/list_view.dart

import 'template.dart';

String listView(List<String> items) {
return template('Table View', _dataTable(items));
}

String _dataTable(List<String> items) {
return """
<table class='table'>
<tr><th>Name</th><th>Action</th></tr>
${items.map(_tableRow).join("")}
</table>
""";
}

String _tableRow(String item) => """<tr>
<td>$item</td>
<td><a href='/view?name=$item'>View Detail</a></td>
</tr>
""";

This contains the entry point to the view as you can see in the listView function which takes all the data you need to render the HTML. That function then gets the template, injects the content and returns the HTML.

Nothing fancy is going on here, its just a table, and then it loops over all the items supplied to render the rows. It creates a dynamic link to a detail view.

Step 3. A detail view

Create a file called /views/detail_view.dart

import 'template.dart';

String detailView(String name) {
return template('Detail View', """
<h1>$name detail</h1>
<a href='/'>Back</a> or <a href='https://www.reddit.com$name'>View subreddit</a>
""");
}

This view renders the value passed through to it and makes a few links. It also injects it into the template.

Step 4. Serve it all up

Create a pubspec.yaml with the following

name: templating_demo

environment:
sdk: '>=2.13.0 <3.0.0'

dependencies:
alfred:

Run pub get and then create the following file in bin/server.dart

import 'dart:io';

import 'package:alfred/alfred.dart';

import '../views/detail_view.dart';
import '../views/list_view.dart';

void main() {
final app = Alfred();

/// Register a HTML type handler to default all strings to HTML
app.typeHandlers.insert(0, htmlTypeHandler);

app.get(
'/',
(req, res) => listView(
["/r/flutterDev", "/r/dartlang", "/r/onlyfans"],
));

app.get(
'/view',
(req, res) =>
detailView(req.uri.queryParameters["name"] ?? 'Name not found'));

app.listen();
}

TypeHandler<String> get htmlTypeHandler =>
TypeHandler<String>((HttpRequest req, HttpResponse res, dynamic value) {
res.headers.contentType = ContentType.html;
res.write(value);
return res.close();
});

This is a simple Alfred server that has two routes — all they do is return the string from the view functions and pass in the data they need.

The other bit is a custom type handler, in this case its just a convenience thing that sets the correct headers to render HTML when a String is returned. Don’t worry too much about that bit.

run dart bin/server.dart and navigate to http://localhost:3000

Viola!

The list screen

If any part of this was too confusing, jump on and check out the complete project on github.

https://github.com/rknell/dart_templating_example

--

--