Serverless functions are server-side functions that are hosted on a third-party service (in this case Netlify). In a nutshell,
- We write our server-side functions
- We walk up to the third-party service with the functions and say, “Oh hey, could you please run these for me on your server?”
- They say “Sure!”
- We can now call the functions to do whatever they are supposed to do and return whatever we need for our web application.
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.
- Basic knowledge in:
- JavaScript and HTML
- How a web application works (server, client/browser)
- RESTful API
- Git version control
- A (free) account on:
- Latest node and npm (run
node -v && npm -v
on your CLI/Terminal to check)
Table of Contents
- Installation
- Start our development environment
- Create our server(less) functions
- 3a. Define our functions directory
- 3b. Add function files to our functions directory
- 3c. Write our first function
- 3d. Write a function with query string parameters
- 3e. Write a function that accepts a data body (payload)
- 3f. Use secret environment variables in our function
1. Installation
1a. Install the global Netlify CLI package
Run on your command line: npm install netlify-cli -g
1b. Prepare your web app
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
.
2. Start our development environment
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.
- Our web app runs on port
8080
- This our web app’s dev server that we usually start with
npm run start
ornpm run watch
(or theyarn
equivalent).
- This our web app’s dev server that we usually start with
- Browsersync UI runs on port
3001
- Our web server runs on port
8888
If you cloned the sample repo, you should see this page on both ports 8080
and 8888
.
What kind of sorcery is this? 🧙🏼♀️✨
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:
- Zero Config, yet Technology Agnostic: How Netlify Dev Detectors Work
- List of built-in detectors https://github.com/netlify/cli/tree/master/src/detectors
- Netlify Dev documentation https://github.com/netlify/cli/blob/master/docs/netlify-dev.md
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!
3. Create our server(less/-side) functions
📝 Note: Netlify Docs has in-depth explanation on the functions discussed here.
3a. Define our functions directory
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"
3b. Add function files to our functions directory
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.
3c. Write our first function
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. - List of HTTP response status codes (maybe don’t return 418).
3d. Write a function with query string parameters
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 parameter -
httpMethod
= request’s method (eg.GET
,POST
,PUT
)- As per the suggested RESTful API naming practices, GET
/.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 thehttpMethod
value.
- As per the suggested RESTful API naming practices, GET
-
headers
= request headers- This contains HTTP Headers data such as
Accept-Language
,User-Agent
, etc.
- This contains HTTP Headers data such as
-
queryStringParameters
= self-explanatory (more on this below) -
body
= request payload -
isBase64Encoded
= a boolean flag to indicate if the applicable request payload is Base64-encode
Now 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!
- We use the
event
parameter and get thename
value fromevent.queryStringParameters
.- Note:
const { name } = event.queryStringParameters
is shorthand forconst name = event.queryStringParameters.name
.
- Note:
- If we have multiple query string parameters in the request, we can access them all as objects in
queryStringParameters
.- eg. GET
localhost:8888/.netlify/functions/hello?name=Eka&timeOfDay=morning
, we can get thename
andtimeOfDay
values fromevent.queryStringParameters
.
- eg. GET
We are not discussing the context
and callback
parameters in this post, but you can read about them in the links below.
Learn more:
- Build serverless functions with JavaScript — Netlify Documentation
- HTTP headers — MDN Docs
3e. Write a function that accepts a data body (payload)
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
.
3f. Use secret environment variables in our function
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.
That’s all for now…
You can see the sample repo below and the demo site here.
ekafyi / hello-serverless-netlify-functions
Sample repo for my “Getting Started with Netlify Functions” posts
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
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!
Bonus Track
With serverless functions, you can send secrets in your requests securely. Here is my favourite (but unfortunately unreleased) version of “Secrets” by Strawberry Switchblade.
Top comments (3)
oh yay! i worked on netlify dev! glad to see you liked it!
It’s awesome and well-documented! (I linked to your post on the Netlify Blog here.)
Even with a new library like JungleJS, only slight modification is needed to make it work.
haha writing that docs took so much time, and its not even great yet 😅 thanks :)