Fetching data in the frontend
Now it's time to get the data into the frontend. If you wish you can copy the demo project below, or simply follow along.
Setting up the demo project
This project uses Netlify functions to securely fetch data from Enterspeed. If you don't wish to use Netlify functions simply move the enterspeed.js
to another folder and remove the netlify-cli
dependency.
Go to our GitHub-repo (https://github.com/enterspeedhq/enterspeed-demos/tree/master/vanilla-js) and clone the project.
cd into the vanilla-js folder and run:
npm install
netlify dev
Create a file called .env.local and insert your environment API key generated in the Enterspeed-app under Environments like this:
ENTERSPEED_PRODUCTION_ENVIRONMENT_API_KEY = [YOUR_ENTERSPEED_API_KEY_HERE];
For a production environment, your API key should be injected on build time or via a serverless function.
Ways of fetching data
Fetching data from Enterspeed can be done in three different ways:
- By using Handle
- By using URL
- By using ID
On our demo site, we’ve used handle and URL.
Handle and ID are good ways of fetching data for “non-site-specific data”, e.g. the navigation. In this demo, we’re using the handle method to fetch the blog list.
We’re using the URL method to fetch all of the individual blog posts.
When the data is fetched you can choose to either generate it client-side, server-side, or at build time. In this demo we're generating it all client-side.
Data can be fetched using the Fetch API or a library like Axios.
In our demo, we have made a component that fetches the data. You’ll find it in the project under “netlify/functions/enterspeed.js”. The components look like this:
require("dotenv").config();
const fetch = (...args) =>
import("node-fetch").then(({ default: fetch }) => fetch(...args));
const API_ENDPOINT = "https://delivery.enterspeed.com/v2";
exports.handler = async (event, context) => {
try {
const response = await fetch(`${API_ENDPOINT}?${event.rawQuery}`, {
headers: {
"Content-Type": "application/json",
"X-Api-key": process.env.ENTERSPEED_PRODUCTION_ENVIRONMENT_API_KEY,
},
});
const data = await response.json();
return { statusCode: 200, body: JSON.stringify({ data }) };
} catch (error) {
console.log(error);
return {
statusCode: 500,
body: JSON.stringify({ error: "Failed fetching data" }),
};
}
};
Creating the website
Setting up the HTML
We start by setting up all the HTML that we need. We're including two libraries:
- Tailwind CSS to style the page
- Navigo to have a simple JavaScript router
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vanilla JS demo</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<script
src="https://cdnjs.cloudflare.com/ajax/libs/navigo/8.11.1/navigo.min.js"
referrerpolicy="no-referrer"
></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css"
referrerpolicy="no-referrer"
/>
<style>
article p {
padding-bottom: 24px;
}
</style>
<script>
// All the JS goes here 🎉
</script>
</head>
<body>
<div
class="px-4 py-5 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8"
>
<div class="ml-2 text-xl font-bold tracking-wide text-gray-800 uppercase">
<a href="/">Enterspeed ♥ Vanilla JS</a>
</div>
</div>
<div class="bg-gray-200">
<div
id="main"
class="px-4 py-16 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8 lg:py-20"
>
<div
id="posts"
class="grid gap-8 lg:grid-cols-3 sm:max-w-sm sm:mx-auto lg:max-w-full"
></div>
</div>
</div>
<div
class="text-center px-t py-8 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8"
>
Demo by
<a
target="_blank"
rel="noreferrer noopener"
href="https://enterspeed.com"
class="hover:text-blue-800"
>
Enterspeed
</a>
</div>
</body>
</html>
Initializing the router
Now that we got the structure in place, let's look at the JavaScript needed. First, let's Initialize our router, which takes one mandatory argument - the root path of our site.
const router = new Navigo("/");
Creating the renderHomepage function
Next, let's create a function that renders our homepage.
const renderHomePage = () => {
fetch("/.netlify/functions/enterspeed?handle=blogList")
.then((response) => response.json())
.then((data) => {
const posts = data.data.views.blogList.content;
posts.forEach((post) => {
const singlePost = `
<a href="/post${post.url}"
class="overflow-hidden transition-shadow duration-300 bg-white rounded border hover:text-blue-800 hover:bg-gray-50"
data-navigo>
<img src="${post.thumbnail}" class="object-cover w-full h-64" alt="${post.title}" />
<div class="p-5 ">
<p class="mb-3 text-xs font-semibold tracking-wide uppercase">
<span class="transition-colors duration-200 text-blue-gray-900">
${post.author.name}
</span>
<span class="text-gray-600">
— ${post.date}
</span>
</p>
<div class="inline-block mb-3 text-2xl font-bold leading-5 transition-colors duration-200">
${post.title}
</div>
<p class="mb-2 text-gray-700">${post.excerpt}</p>
</div>
</a>`;
document.getElementById("posts").innerHTML += singlePost;
});
})
.then(() => {
router.updatePageLinks();
router
.on("/post/:postLink", ({ data }) => {
renderPostPage(data.postLink);
})
.resolve();
});
};
The first thing we do in the function is to fetch content via our enterspeed.js
function.
We include the query ?handle=blogList
since we want to map out the blog posts in a nice grid overview.
The content is stored in data.data.views.blogList.content
, which we assign to a const called posts
.
fetch("/.netlify/functions/enterspeed?handle=blogList")
.then((response) => response.json())
.then((data) => {
const posts = data.data.views.blogList.content;
Next, we iterate over each of these posts. We create the HTML for the "card" and map the data appropriately. Then we take each card and add it to the posts
HTML element:
<div
id="posts"
class="grid gap-8 lg:grid-cols-3 sm:max-w-sm sm:mx-auto lg:max-w-full"
></div>
posts.forEach((post) => {
const singlePost = `
<a href="/post${post.url}"
class="overflow-hidden transition-shadow duration-300 bg-white rounded border hover:text-blue-800 hover:bg-gray-50"
data-navigo>
<img src="${post.thumbnail}" class="object-cover w-full h-64" alt="${post.title}" />
<div class="p-5 ">
<p class="mb-3 text-xs font-semibold tracking-wide uppercase">
<span class="transition-colors duration-200 text-blue-gray-900">
${post.author.name}
</span>
<span class="text-gray-600">
— ${post.date}
</span>
</p>
<div class="inline-block mb-3 text-2xl font-bold leading-5 transition-colors duration-200">
${post.title}
</div>
<p class="mb-2 text-gray-700">${post.excerpt}</p>
</div>
</a>`;
document.getElementById("posts").innerHTML += singlePost;
});
Notice that we have inserted data-navigo
in the a
tag. This is so Navigo attaches a click handler which fires the router's navigate method.
Finally, to get Navigo to recognize these links we just created, we call the updatePageLinks
method. When any of the link which has the data-navigo
attribute gets clicked, it should call the renderPostPage
function, which we will create next, and give it the post link as an argument.
.then(() => {
router.updatePageLinks();
router
.on("/post/:postLink", ({ data }) => {
renderPostPage(data.postLink);
})
.resolve();
});
Now, before moving on to the renderPostPage
function, let's remember to initialize our renderHomepage
function by calling it.
renderHomePage();
Creating the renderPostPage function
const renderPostPage = (postLink) => {
fetch(`/.netlify/functions/enterspeed?url=/${postLink}`)
.then((response) => response.json())
.then((data) => {
const post = data.data.route.content;
const postPage = `
<div class="px-4 py-5 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-lg md:px-24 lg:px-8">
<div class="text-center">
<h1
class="max-w-lg mb-6 font-sans text-3xl font-bold leading-none tracking-tight text-gray-900 sm:text-4xl md:mx-auto">
${post.title}
</h1>
<div>
${post.author.name} - ${post.date}
</div>
</div>
<img src="${post.featuredImage}" alt"=${post.title}" class="rounded shadow-lg my-8 mx-auto" />
<article id="article" class="text-gray-700 text-lg blogpost">
</article>
<div class="block mt-4 mb-8 text-lg font-bold text-gray-800 hover:text-blue-800">
<a href="/">👈 Go back</a>
</div>
</div>`;
document.getElementById("main").innerHTML = postPage;
document.getElementById("article").innerHTML = post.content;
});
};
Just like before, we fetch the content via our enterspeed.js
function. We use url=
instead of handle=
, since we set the route to url
in our blog posts schema.
The content here is stored in data.data.route.content
, which we assign to a const called post.
We then create the HTML for the "post" and map the data appropriately.
Lastly, we take our postPage
and add it to our main
HTML element:
<div
id="main"
class="px-4 py-16 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8 lg:py-20"
></div>
...as well as the article itself and add to the article HTML element:
<article id="article" class="text-gray-700 text-lg blogpost"></article>
Now when navigating to one of the blog posts, we will have a nice, simple article page.
The finished results should look something like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vanilla JS demo</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<script
src="https://cdnjs.cloudflare.com/ajax/libs/navigo/8.11.1/navigo.min.js"
referrerpolicy="no-referrer"
></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css"
referrerpolicy="no-referrer"
/>
<style>
article p {
padding-bottom: 24px;
}
</style>
<script>
const router = new Navigo("/");
const renderPostPage = (postLink) => {
fetch(`/.netlify/functions/enterspeed?url=/${postLink}`)
.then((response) => response.json())
.then((data) => {
const post = data.data.route.content;
const postPage = `
<div class="px-4 py-5 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-lg md:px-24 lg:px-8">
<div class="text-center">
<h1
class="max-w-lg mb-6 font-sans text-3xl font-bold leading-none tracking-tight text-gray-900 sm:text-4xl md:mx-auto">
${post.title}
</h1>
<div>
${post.author.name} - ${post.date}
</div>
</div>
<img src="${post.featuredImage}" alt"=${post.title}" class="rounded shadow-lg my-8 mx-auto" />
<article id="article" class="text-gray-700 text-lg blogpost">
</article>
<div class="block mt-4 mb-8 text-lg font-bold text-gray-800 hover:text-blue-800">
<a href="/">👈 Go back</a>
</div>
</div>`;
document.getElementById("main").innerHTML = postPage;
document.getElementById("article").innerHTML = post.content;
});
};
const renderHomePage = () => {
fetch("/.netlify/functions/enterspeed?handle=blogList")
.then((response) => response.json())
.then((data) => {
const posts = data.data.views.blogList.content;
posts.forEach((post) => {
const singlePost = `
<a href="/post${post.url}"
class="overflow-hidden transition-shadow duration-300 bg-white rounded border hover:text-blue-800 hover:bg-gray-50"
data-navigo>
<img src="${post.thumbnail}" class="object-cover w-full h-64" alt="${post.title}" />
<div class="p-5 ">
<p class="mb-3 text-xs font-semibold tracking-wide uppercase">
<span class="transition-colors duration-200 text-blue-gray-900">
${post.author.name}
</span>
<span class="text-gray-600">
— ${post.date}
</span>
</p>
<div class="inline-block mb-3 text-2xl font-bold leading-5 transition-colors duration-200">
${post.title}
</div>
<p class="mb-2 text-gray-700">${post.excerpt}</p>
</div>
</a>`;
document.getElementById("posts").innerHTML += singlePost;
});
})
.then(() => {
router.updatePageLinks();
router
.on("/post/:postLink", ({ data }) => {
renderPostPage(data.postLink);
})
.resolve();
});
};
renderHomePage();
</script>
</head>
<body>
<div
class="px-4 py-5 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8"
>
<div class="ml-2 text-xl font-bold tracking-wide text-gray-800 uppercase">
<a href="/">Enterspeed ♥ Vanilla JS</a>
</div>
</div>
<div class="bg-gray-200">
<div
id="main"
class="px-4 py-16 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8 lg:py-20"
>
<div
id="posts"
class="grid gap-8 lg:grid-cols-3 sm:max-w-sm sm:mx-auto lg:max-w-full"
></div>
</div>
</div>
<div
class="text-center px-t py-8 mx-auto sm:max-w-xl md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8"
>
Demo by
<a
target="_blank"
rel="noreferrer noopener"
href="https://enterspeed.com"
class="hover:text-blue-800"
>
Enterspeed
</a>
</div>
</body>
</html>