Hello Netlify Functions
Sample repo for the first post in my “Getting Started with Netlify Functions” series.
Demo:
- Website - https://hello-serverless-netlify-functions-one.netlify.app
- Function
Posted on July 25, 2020
Serverless functions are server-side functions that are hosted on a third-party service (in this case Netlify). In a nutshell,
Why? With serverless functions, we get the benefits of a server without having our own server. Our “server-side” functions can even live in the same codebase as our website code. How convenient!
Should I use these? In my opinion, this is ideal for small to mid-level projects. If your app has large traffic or other specific/unusual requirements, make sure to compare the costs and implications thoroughly before going serverless.
A misnomer? Much like wireless speakers are not without wires, these functions definitely run on servers; we just don’t need to set up, configure, host, and manage them ourselves. 😁
In this series, we’re going to learn how Netlify functions work by building a web application with a REST API from scratch and deploy it. It’s inspired by Jason Lengstorf’s “Introduction to Serverless Functions” workshop, which was my first encounter with serverless functions.
This first post focuses on getting our local dev environment up and writing our first functions. Before we proceed, please note this post presumes the following.
node -v && npm -v
on your CLI/Terminal to check)Table of Contents
Run on your command line: npm install netlify-cli -g
Clone or initialize your project. You can clone this sample repo for a quick start.
git clone -b part-1 https://github.com/ekafyi/hello-serverless-netlify-functions.git
Netlify functions are written in standard (vanilla) JS and work with any modern JS libraries. The sample repo uses the default Eleventy installation. We're not going to write any web code yet, but feel free to use a starter from your favourite library instead.
Don’t forget to cd
to the project directory and install the project’s dependencies by running yarn
or npm install
.
Run netlify dev
(or ntl dev
for short) to start our local development environment. It’s going to serve our web app (in my case Eleventy), our web server, and a Browsersync interface.
8080
npm run start
or npm run watch
(or the yarn
equivalent).3001
8888
If you cloned the sample repo, you should see this page on both ports 8080
and 8888
.
Netlify Dev is an integrated local development environment that automatically:
- Detects and runs your site generator
- Makes environment variables available
- Performs edge logic and routing rules
- Compiles and runs cloud functions
It has a “project detector” that checks our project package.json
and configures the dev setup automatically. In most cases, it “just works”—no need to do anything.
However, if you use a less popular library and/or have a custom setup, you can declare your setup by creating a netlify.toml
file in your project root that contains your dev setup.
# netlify.toml dev block example
[dev]
command="yarn dev"
port=5000
Learn more:
Note: If you run extra servers for custom processes (eg. to watch CSS with Sass or PostCSS), you still need to run them separately in addition to ntl dev
, OR modify your package.json
so they are included in the default dev command.
Now we’ve got our web app running, let’s write our first functions!
📝 Note: Netlify Docs has in-depth explanation on the functions discussed here.
I create a new directory called functions
in my project root. (You can use any name; make sure to specify the right name below.)
Create a file called netlify.toml
in your project root (if you don’t have it already) and add functions=""
inside the [dev]
block, where the value is the name of our serverless functions directory (in my case, functions
).
# netlify.toml
[dev]
command="npm run build"
publish="_site"
functions="functions"
Next, create a JS file in our functions directory that exports a handler
method. (I use the ES6 syntax here, hence () => { … }
instead of function() { … }
. Feel free to use whichever you’re most comfortable with.)
// functions/hello.js
exports.handler = () => {
// ... later
}
The filename without extension represents a serverless function endpoint in this format: {SERVER_URL}/.netlify/functions/{FUNCTION_NAME}
.
So, if we have functions/hello.js
, we send our request to http://localhost:8888/.netlify/functions/hello
.
We can have as many files as we want.
Our function should return a JSON response. This is the most simple function possible.
// functions/hello.js
exports.handler = async () => {
return {
statusCode: 200,
body: "Hello there!",
};
};
// GET localhost:8888/.netlify/functions/hello
// → Hello there!
Notes:
async
means this is an asynchronous function, which I can call from my web app code with a promise. If you’re not familiar with these, read “Introducing asynchronous JavaScript“ on MDN.The handler method has the event
and context
parameters, and an optional callback
parameter. The event
object contains information about the request. It looks like this.
{
"path": "/.netlify/functions/echo",
"httpMethod": "GET",
"headers": { ... },
"queryStringParameters": {},
"body": undefined,
"isBase64Encoded": false
}
path
= path parameterhttpMethod
= request’s method (eg. GET
, POST
, PUT
)
/.netlify/functions/users
should get the users data and POST /.netlify/functions/users
should create a new user. We can do this in serverless functions by getting the httpMethod
value.headers
= request headers
Accept-Language
, User-Agent
, etc.queryStringParameters
= self-explanatory (more on this below)body
= request payloadisBase64Encoded
= a boolean flag to indicate if the applicable request payload is Base64-encodeNow let’s modify our hello
function to return a greeting based on the name
parameter in the request.
// functions/hello.js
exports.handler = async (event) => {
const { name } = event.queryStringParameters;
return {
statusCode: 200,
body: `Hello ${name || 'stranger'}!`,
};
};
// GET localhost:8888/.netlify/functions/hello
// → Hello stranger!
// GET localhost:8888/.netlify/functions/hello?name=Eka
// → Hello Eka!
event
parameter and get the name
value from event.queryStringParameters
.
const { name } = event.queryStringParameters
is shorthand for const name = event.queryStringParameters.name
.queryStringParameters
.
localhost:8888/.netlify/functions/hello?name=Eka&timeOfDay=morning
, we can get the name
and timeOfDay
values from event.queryStringParameters
.We are not discussing the context
and callback
parameters in this post, but you can read about them in the links below.
Learn more:
POST requests are basically similar to GET requests, except they may contain data—eg. user-inputted form data—in the request body. We can access it in event.body
and do something with it, such as send it to some external API or database.
// functions/newsletter.js
exports.handler = async (event) => {
const { body } = event; // Request body data
// Basic example of sending the data to an external API
fetch(`https://someserver.com/v1/some/endpoint`, {
method: "POST",
headers: {
"Content-type": "application/json",
// Add credentials as required by the external service
},
body: body, // Send the data
})
.then((response) => {
// Do stuff and returns 200 response...
})
.catch((error) => {
// Do stuff and returns 4xx / 5xx response...
});
};
// POST localhost:8888/.netlify/functions/newsletter
// → 200 OK
Remember that these functions are regular JavaScript code. body
data are sent in JSON-safe string format; request body
must be parsed if we want to access it as JS objects, and contrariwise JS objects must be stringified to send in response body
.
We briefly discussed sending our data to an external service in the example above. Today, most web apps retrieve and/or send data to and from external APIs, usually with some kind of secret (token or key) in our request header to validate our app.
We should not make this request from the client side (the browser), because everyone can open the browser dev tools and find your credentials 🙀, which then can be used for (eg.) deleting all your data or posting egregious content on your behalf.
With serverless functions, we can have “server-side” code to handle this operation while keeping our credentials private.
Create .env
in your project root (if it does not exist yet) and add your secret there.
SPOTIFY_CLIENT_ID=xxxxxxxxx
SPOTIFY_CLIENT_SECRET=xxxxxxxxx
The env data is available in our functions in process.env
like so.
// functions/spotify.js
exports.handler = async (event) => {
// Example of Spotify API "Client Credentials" authorization
const getToken = async () => {
const result = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "Basic " + btoa(process.env.SPOTIFY_CLIENT_ID + ":" + process.env.SPOTIFY_CLIENT_SECRET),
},
body: "grant_type=client_credentials",
});
const data = await result.json();
return data;
};
getToken()
.then((response) => {
// Do stuff ...
})
.catch((error) => {
// Do stuff ...
});
};
Make sure .env
is in your .gitignore
so it does not get pushed to the repository.
You can see the sample repo below and the demo site here.
Sample repo for the first post in my “Getting Started with Netlify Functions” series.
Demo:
We have set up our project’s local development environment, written our first serverless functions, but you may notice we haven’t done anything with our web app. We’re going to do it in the next post, so stay tuned!
With serverless functions, you can send secrets in your requests securely. Here is my favourite (but unfortunately unreleased) version of “Secrets” by Strawberry Switchblade.
Posted on July 25, 2020
Sign up to receive the latest update from our blog.
July 25, 2020