Itsiest, Bitsiest Eleventy Tutorial
Want to get started with Eleventy but feel overwhelmed? Try out this pared-down tutorial
Get started going serverless with Netlify functions and Netlify-CLI for keeping secrets secret
This post is a companion to the talk I give on buidling serverless functions with Netlify (slides). I usually give it to groups newer to web development such as bootcampers.
My goal here is to give a step-by-step tutorial for those wanting to duplicate my demo, or anyone interested in an intro tutorial on the topic! This post will walk through:
To get the most out of this tutorial, it's best if you're already familiar with HTML, JavaScript, npm, and the command line.
We are going to build the first feature of my AstroBirthday site. Namely, when a user selects a date and clicks the button, we will fetch NASA's Astronomy Picture of the Day (APOD for short) for that date. This app will use the free API from NASA (rate-limited). We will generate an API key, store it securely, make the API request, and send the data to our front end to render.
Before we dive into the how, let's learn the what. In serverless applications (from Serverless Architecture):
That last point is a big one for me. I love developing web apps, but thinking about managing a server makes my head hurt.
Jamstack is...
A modern web development architecture based on client-side JavaScript, reusable APIs, and prebuilt Markup
—Mathias Biilmann (CEO & Co-founder of Netlify), from jamstack.wtf/
The following figure from the Netlify site shows us how all the pieces fit together. First, we have a standard front end built with HTML, CSS, JavaScript and other assets like images. A provider like Netlify can handle automatic builds and deploys among other CI/CD things. Finally, instead of a full-featured backend, we use API's like a set of services to get the features we want. Those APIs can be external like Stripe for payments, Sanity.io for CMS, and Hasura Cloud for databases.
Our frontends can be vanilla HTML, CSS, and JavaScript, but we can also use many different frameworks which are capable of building static pages. Prebuilding pages at build time instead of at request time makes our site faster.
I first came to love Netlify because deploying static sites was so much easier than some of the other methods I was using. You'll get to see how in the next sections. No more manual builds and file uploading nor complex continual integration setups! My deployment steps consist of just one step now - git push
.
Other features that got me to love Netlify even more are:
This is all before I started using it for serverless functions. Are you a fan of Netlify? Tell me your favorite features in my webmentions!
Enough of the chit-chat, let's get started. If you've already deployed to Netlify, you can skip this step. Otherwise, here is the most basic way to deploy a site to Netlify. You create a GitHub repo, connect to it inside Netlify, and voilà, it's live!
<h1>Hello, Netlify</h1>
.https://elastic-puppy-7a396e.netlify.app/
Now whenever you update the main
branch (or whichever branch you pointed Netlify to) on GitHub, the site will automatically deploy. You can also use GitLab or BitBucket instead of GitHub.
Now we're going to put it on steroids to make it even more powerful.
Netlify CLI lets us more easily build serverless functions in a dev environment and then port them to production. We can also do other things like manage secrets between both environments.
To get started, install it on the command line and login:
$ npm install netlify-cli -g
$ netlify
$ netlify login
Your install message will look something like this:
Success! Netlify CLI has been installed!
Your device is now configured to use Netlify CLI to deploy and manage
your Netlify sites.
Next steps:
netlify init Connect or create a Netlify site from current
directory
netlify deploy Deploy the latest changes to your Netlify site
For more information on the CLI run netlify help
Or visit the docs at https://cli.netlify.com
I don't usually use netlify deploy
since I usually commit my code and push to GitHub to trigger deploys instead. However, netlify init
is a handy way to create a new Netlify site with a repo. If you type netlify
and hit enter, you'll see all the command options. Here are a few I use:
COMMANDS
dev Local dev server
init Configure continuous deployment for a new or existing site
login Login to your Netlify account
open Open settings for the site linked to the current folder
To learn more, check out Get started with Netlify CLI.
Let's get started with Netlify CLI by adding it to our demo project! Fork, clone, and install dependencies. Then, run netlify init
:
cd serverless-netlify-demo
npm install
netlify init
and answer the questions. For this project, the build command is npm run build
and the deploy directory is _site
. Answer "yes" when it asks to generate a netlify.toml file.netlify open
to have a browser window opening to your site's page on the Netlify dashboard. You'll also be able to see the URL generated for your site. Note that adding a custom domain is free on Netlify.Now we can really get started!
Before we build our serverless function to request the Astronomy Picture of the Day, we need to get an API key and store it securely.
netlify open
to go to the Netlify UINASA_API_KEY
in Build & Deploy > Environmentnetlify dev
to confirm it gets injected for local devIt's a good idea to update your start
script in your package.json file to use netlify dev
.
Let's talk about Netlify's flavor of serverless functions which they simply call functions:
At the time of writing, the free tier included:
Make sure to check Netlify's current pricing.
To get started, let's check our netlify.toml file generated when we ran netlify init
. This file sets up the configuration for our Netlify builds. I think the default folder for functions is netlify/functions, but I simplified mine to only functions:
[build]
command = "npm run build"
publish = "_site"
functions = "functions"
This tells Netlify where to look for our functions. It will sync with our settings and point to where the functions can be found in our project. Now we can:
In the tradition of our people, let's write our first Hello World function. Create a new file in the /functions/ folder callled hello.js:
// functions/hello.js
// Functions must export a handler with this syntax:
exports.handler = async function(event, context) {
// Log our parameters so we can check them out later
console.log({event, context})
// At a minimum, you must return an object with an HTTP status code to prevent timeouts:
return {
statusCode: 200,
body: JSON.stringify({message: "Hello World"})
};
}
We'll talk about what's in the event and context objects in a bit. To learn more about functions, read Build serverless functions with JavaScript in the docs.
Now that we have our function endpoint, we can use it in our front end. In src/index.html, add this script to the bottom of the file:
<!-- src/index.html -->
<!-- At bottom of file: -->
<script>
// Fetch our serverless function endpoint via GET
fetch('/.netlify/functions/hello')
.then(response => response.json())
.then(json => console.log({json}))
</script>
You may need to restart your server (netlify dev
), then check the browser console for the value of {json}
. If your code isn't working, compare it against the 2-hello-functions
branch of the demo.
What about those console.log
statements in our hello.js function? Well, those are technically on the server side so you will not see them in the browser console. We can find them in our command line/terminal console.
The event
parameter gives us important data from the request like the path, headers, any query string parameters, and the body:
{
"path": "Path parameter",
"httpMethod": "Incoming request's method name"
"headers": { /* Incoming request headers */ }
"queryStringParameters": { /* query string parameters */ }
"body": "A JSON string of the request payload."
"isBase64Encoded": /* A boolean flag to indicate if the applicable request payload is Base64-encode */
}
The context
parameter includes information about the context in which the serverless function was called, like certain Identity user information, for example.
What can we return from our serverless functions? We've seen status code and body, but we can also send headers among other things:
{
"isBase64Encoded": /* true|false */,
"statusCode": httpStatusCode,
"headers": { "headerName": "headerValue", ... },
"multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
"body": "..."
}
Finally, we can build our feature to fetch the Astronomy Picture of the Day.
First, in src/index.html, add an async function to handle the form submission which will fetch from our serverless function endpoint. For now, we'll just log the response to the console. Don't forget to add the event listener.
// index.html, inside a <script> tag
async function handleSubmit(e) {
e.preventDefault()
const formData = new FormData(e.target)
const date = formData.get("date")
// Pass the date into the request body
fetch('/.netlify/functions/apod', {
method: "POST",
body: JSON.stringify({date})
})
.then(response => {
// Bad network responses do not automatically throw errors
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json()
})
.then(json => {console.log(json)})
.catch(error => {console.error(error)})
}
// Event listener
document.querySelector("#birthday-form").addEventListener("submit", handleSubmit)
Now let's build out the "backend" function. Node does not have fetch available in its api so we need to install it in our project:
$ npm install node-fetch --save
Then, we can use fetch in our serverless function. First we'll grab the date from the event's body then use it plus our API key stored in Netlify to fetch the APOD for that date.
// functions/apod.js
const fetch = require("node-fetch")
exports.handler = async function(event, context) {
const BASE_URL = "https://api.nasa.gov/planetary/apod"
// Get the date from the event body
const {date} = JSON.parse(event.body)
// Access the environment variables using process.env and request the APOD
return fetch(`${BASE_URL}?api_key=${process.env.NASA_API_KEY}&date=${date}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json()
})
.then(data => {
// Return OK status and the data from the response
return {
statusCode: 200,
body: JSON.stringify(data)
}
})
.catch(error => {
// Return a network error and the text of the error
return {
statusCode: 500,
body: JSON.stringify({error})
}
})
}
Save everything, run npm start
, select a date in the form, and hit submit. If everything works, you'll have the data logged to the browser console!
Let's finish up this feature by rendering our data so users can see those pretty pictures. <template>
tags are really handy in vanilla JS. They let us clone an HTML template and then simply fill in the missing attributes like image src
rather than creating all the markup in JavaScript:
// index.html, inside the <script>
// Don't allow selecting the future
document.querySelector("#date-input").max = new Date().toLocaleDateString('en-CA')
function render(data) {
// The target is where we will "dump" our APOD component onto the page
const target = document.querySelector("#apods");
// Clear it out (in case this is not the first time one was requested)
target.innerHTML = ""
// Grab the template for the APOD component
const template = document.querySelector(".apod-template");
// Clone the template and fill in the key data with our APOD response data
const clone = template.content.cloneNode(true);
clone.querySelector("img").src = data.url
clone.querySelector("h2").innerText = data.title
clone.querySelector(".explanation").innerText = data.explanation
// Support the original photographers by showing copyrights if applicable
if (data.copyright) {
clone.querySelector(".copyright").innerText = data.copyright
clone.querySelector("small").classList.remove("hidden")
}
// Dump our APOD component into the target
target.appendChild(clone);
}
// ... rest of code including handleSubmit function
Now, we can replace the console.log(json)
of our data with a call to render(json.data)
inside the handleSubmit
function. Save, refresh, and test it out! If your code isn't working, compare it against the 3-apod-function
branch of the demo.
Hopefully this post gave you a better understanding of how serverless functions work, especially on Netlify.
If you'd like a more in-depth workshop that also covers authentication and CRUD operations using a cloud-based GraphQL database, I highly recommend Jason Lengtstorf's course Introduction to Serverless Functions on Frontend Masters. The LearnWithJason livestream also frequently covers serverless topics like Sell Products on the Jamstack.
I'm a freelance performance engineer and web developer, and I'm available for your projects.
Hire meWant to get started with Eleventy but feel overwhelmed? Try out this pared-down tutorial
Add Prettier with a pre-commit hook and dedicate one commit to a full reformat
Setting and using data in the static site generator Eleventy
If you liked this article and think others should read it, please share it.
These are webmentions via the IndieWeb and webmention.io. Mention this post from your site: