A Practical Introduction to Jamstack
Martins Onuoha
Posted on May 3, 2023
What is Jamstack
If you’re into Web Development and have worked with one or two Javascript Frameworks at one point, you most likely have come across the terms MERN, MEAN, and, most recently, MEVN Stack. MongoDB serves as your non-relational database in these stacks, while ExpressJS and Nodejs are your web server. One similarity between these stacks is the presence of a web server. This is where the Jamstack differs.
JAM stands for Javascript API Markup, an abbreviation of the technologies used in building a web application. Here’s a pretty straightforward explanation of what Jamstack is from the official Jamstack website:
A modern architecture for creating fast, secure sites and dynamic apps with JavaScript, APIs, and pre-rendered Markup, served without web servers*.*
Why is it necessary
From the definition, “without web servers” is in bold text because this is the basic idea of Jamstack. With Jamstack, front-end developers can build useful, functional web applications without needing a backend.
With just your front-end skill, you can build awesome useful applications thanks to the Jamstack specification.
Some examples of existing sites built with Jamstack are:
To learn more about Jamstack, check out the official Jamstack website: jamstack.org
A Practical Example
We’ll be building Chucklarious. Chucklarious is a collection of random Chuck Norris jokes. Not the most useful application, I know, but it’s a basic illustration of what Jamstack is about.
Prerequisites
Basic knowledge of HTML, CSS, and Javascript
Familiarity with Javascript’s fetch API.
We’ll be working with the open-source Internet Chuck Norris Database API.
Folder Structure
We have a basic folder structure, so you can ignore the img folder.
Markup
From the screenshot of Chucklarious, you’d notice we have a basic structure of top Navbar, Cards, and a FAB at the bottom right.
I’ll be using MaterializeCSS; however, feel free to use whatever you want for styling.
For Content rendering and semantic templating, I’ll be using Handlebarsjs
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Chucklarious</title>
<link rel="shortcut icon" href="https://www.demilked.com/magazine/wp-content/uploads/2016/06/gif-animations-replace-loading-screen-14.gif" />
<link href="./css/style.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
If you checked the live example, you would notice a preloader just before the page loads. The Navbar comes right after the preloader.
<body class="grey lighten-2">
<div class="center-align" id="preloader"></div>
...
...
<div class="navbar-fixed">
<nav>
<div class="nav-wrapper purple lighten-1">
<ul id="nav-mobile" class="left hide-on-med-and-down">
<li><a href="https://github.com/martinsOnuoha">GitHub</a></li>
<li><a href="#">LinkedIn</a></li>
<li><a href="#">Medium</a></li>
<li><a href="#">Original</a>
</li>
</ul>
</div>
</nav>
</div>
...
Next, we’ll build out content to show the user in case of failed page load.
<div id="container" class="container">
<div class="row" id="render_here">
<div id="failure" class="col s12">
<h5 class="center-align">This isn't funny we know, but Something went wrong. :(</h5>
</div>
</div>
</div>
For content rendering, we’re using Handlebars. Handlebarjs is a minimal templating engine. Handlebars templates look like regular HTML, with embedded handlebars expressions. Those expressions have a format like {{ some expression }}
. These expressions make it easy to render contents dynamically, one of which is {{ each }}
which works like a regular for loop in Javascript.
<script id="entry-template" type="text/x-handlebars-template">
{{ #each jokes }}
<div class="col m6 s12">
<div class="card white hoverable" id="joke{{ id }}">
<div class="card-content white-text">
<p class="flow-text">{{ joke }}</p>
</div>
<div class="card-action">
<button id="btn-{{ id }}" class="btn waves-effect waves-light purple lighten-1"
onclick="lmfao(this.id)">LMFAO
<i class="material-icons right">star</i>
</button>
</div>
</div>
</div>
{{ /each }}
</script>
Notice we have a button for reacting to jokes. I’ve attached an on-click event to the button, called the LMFAO (terrible choice of name, by the way) function, and passed the joke id as an argument.
<div class="fixed-action-btn toolbar">
<a id="menu" class="btn-floating btn-large purple darken-2">
<i class="large material-icons">menu</i>
</a>
<ul>
<li class="waves-effect waves-light">
<a href="#!" onclick="reloadJokes()">
<i class="material-icons">autorenew</i>
</a>
</li>
<li class="waves-effect waves-light">
<a class="modal-trigger" href="#!" data-target="closeModal">
<i class="material-icons">power_settings_new</i>
</a>
</li>
</ul>
</div>
<!-- Tap Target Structure -->
<div class="tap-target" data-target="menu">
<div class="tap-target-content white-text">
<h5>Info</h5>
<p>Insert an Info here</p>
</div>
</div>
<!-- end Tap Target -->
We would also need a prompt just before the user closes the application.
<div id="closeModal" class="modal">
<div class="modal-content">
<h4>Sure?</h4>
<p>You're about to stop laughing...</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-green btn-flat left">Nah</a>
<a href="#!" onclick="window.close()" class="waves-effect waves-green btn-flat right">
Yes, I've had enough
</a>
</div>
</div>
Finally, we include the scripts at the bottom, right before the closing body tag:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.12/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="./js/script.js"></script>
Styles
Let’s add a bit of styling for the preloader and card content. Add this to style.css
:
#failure {
margin-top: 10em;
color: #CCC;
display: none;
}
#preloader {
position: fixed;
z-index: 99999999999;
top: 0;
left: 0;
overflow: visible;
width: 100%;
height: 100%;
background: #fff url("https://www.demilked.com/magazine/wp-content/uploads/2016/06/gif-animations-replace-loading-screen-14.gif") no-repeat center center;
}
.flow-text {
color: grey;
}
.card-content {
min-height: 220px;
max-height: 220px;
overflow-y: scroll;
}
API
I mentioned earlier that we would be using the icndb.comAPI. They provide an endpoint to fetch random jokes while limiting the number of jokes to fetch per request. The endpoint would look like this:
https://api.icndb.com/jokes/random/10
Let’s see what the structure of our JSON response would look like:
{ "type": "success",
"value": [
{ "id": 489, "joke": "Chuck Norris can write multi-threaded applications with a single thread.", "categories": ["nerdy"] },
{ "id": 513, "joke": "Chuck Norris does not code in cycles, he codes in strikes.", "categories": ["nerdy"] },
{ "id": 342, "joke": "Chuck Norris owns a chain of fast-food restaurants throughout the southwest. They serve nothing but barbecue-flavored ice cream and Hot Pockets.", "categories": [] },
{ "id": 271, "joke": "Staring at Chuck Norris for extended periods of time without proper eye protection will cause blindess, and possibly foot sized brusies on the face.", "categories": [] },
{ "id": 306, "joke": "Scientifically speaking, it is impossible to charge Chuck Norris with "obstruction of justice." This is because even Chuck Norris cannot be in two places at the same time.", "categories": [] },
{ "id": 303, "joke": "Fact: Chuck Norris doesn't consider it sex if the woman survives.", "categories": [] },
{ "id": 296, "joke": "Chuck Norris uses 8'x10' sheets of plywood as toilet paper.", "categories": [] },
{ "id": 184, "joke": "If at first you don't succeed, you're not Chuck Norris.", "categories": [] },
{ "id": 212, "joke": "Chuck Norris does not play the lottery. It doesn't have nearly enough balls.", "categories": [] },
{ "id": 619, "joke": "Chuck Norris can lock a safe and keep the key inside it.", "categories": [] }
]
}
First, In our js/script.js
we’d handle fading in and out of the preloader and toggling the FAB.
(function ($) {
jQuery(window).on('load', function(){
jQuery("#preloader").fadeOut(4000);
});
$('.fixed-action-btn').floatingActionButton({
toolbarEnabled: true
});
$(document).ready(function(){
$('.modal').modal();
});
}(jQuery));
Next, we call the getData
and showTip
functions once the page is done loading:
document.addEventListener("DOMContentLoaded", function () {
getData();
showTip();
});
Let’s implement the getData
function:
Using the Javascript fetch function, we’ll send a GET request to the endpoint, get the JSON response, and set it as the value of the “jokes” key of the context object. We’ll also grab the content of the script element and compile it into a template so it can be executed. Finally, we render the compiled template:
function getData() {
fetch("https://api.icndb.com/jokes/random/10")
.then(res => res.json()).then(data => {
if (data.type == "success") {
let source = document.getElementById("entry-template").innerHTML;
let template = Handlebars.compile(source);
var context = {
jokes: data.value
}
let html = template(context);
$('#render_here').html(html);
} else if (!data || (response.status != 200)) {
$('#failure').show();
}
});
}
Next, we implement the showTip
function:
function showTip() {
$('.tap-target').tapTarget();
$('.tap-target').tapTarget('open');
}
Finally, we write a function to reload the jokes and another to react to a joke:
function reloadJokes() {
getData();
window.scrollTo(0, 0);
}
To react to a joke:
function lmfao(_id) {
// Cache awesome stuff here.
$(`#${_id}`).addClass('disabled');
M.toast({html: 'Fucking hillarious!'});
}
If you’ve come this far, I’d expect you now understand the idea behind Jamstack and have successfully replicated Chucklarious. 🎉🎉🎉
Be sure to read more on the Jamstack ecosystem and get your hands dirty with practice.
You can find a live example here. The source code also lives here.
Posted on May 3, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.