DEV Community


Getting Started with Netlify Functions — Part 1: Zero-config setup and writing our first functions

ekafyi profile image Eka ・8 min read

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. 😁

Finn from the cartoon Adventure Time doing a thumbs-up with the text Niiiiice

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

  1. Installation
  2. Start our development environment
  3. 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

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.

ntl dev serving 3 URLs in the CLI

  • Our web app runs on port 8080
    • This our web app’s dev server that we usually start with npm run start or npm run watch (or the yarn equivalent).
  • 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.

web app home page

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
  command="yarn dev"

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!

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
  command="npm run build"

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!


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 the httpMethod value.
  • headers = request headers
    • This contains HTTP Headers data such as Accept-Language, User-Agent, etc.
  • 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 the name value from event.queryStringParameters.
    • Note: const { name } = event.queryStringParameters is shorthand for const name =
  • 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 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:

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(``, {
    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.


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("", {
      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;

    .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.

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.

Discussion (3)

Editor guide
swyx profile image

oh yay! i worked on netlify dev! glad to see you liked it!

ekafyi profile image
Eka Author

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.

swyx profile image

haha writing that docs took so much time, and its not even great yet 😅 thanks :)