Netlify Functions are a surprisingly effective way to run backend code without managing servers, but they aren’t just a magic button for your API.
Let’s see one in action. Imagine you want a simple function that returns the current time.
Create a .netlify/functions directory in your project’s root. Inside that, create a file named time.js.
// .netlify/functions/time.js
exports.handler = async (event, context) => {
const now = new Date();
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: `The current time is: ${now.toISOString()}`,
}),
};
};
When you deploy your site to Netlify, this function becomes available at /.netlify/functions/time (or, if you’ve configured a custom path, at your chosen API endpoint). A GET request to this URL will trigger the handler function.
The core problem Netlify Functions solve is abstracting away the operational burden of running backend code. You write JavaScript (or other supported runtimes), and Netlify handles provisioning, scaling, and maintaining the underlying infrastructure. This means you can focus on writing the logic for your API endpoints, background jobs, or cron tasks without worrying about server maintenance, security patches, or capacity planning.
Internally, Netlify Functions are powered by AWS Lambda. When you deploy a function, Netlify packages your code and its dependencies into a Lambda function. Netlify’s build process detects these functions and deploys them to AWS. Netlify then acts as a proxy, routing incoming requests to the appropriate Lambda function and returning the response to the client. This means you get the benefits of a robust, scalable serverless platform without direct AWS configuration.
The event object passed to your handler contains information about the incoming request, such as httpMethod, path, headers, and queryStringParameters. The context object provides information about the execution environment. Your function must return an object with statusCode, headers, and body. The body should be a string, which is why we JSON.stringify() our response.
The netlify.toml file is crucial for configuring your functions. You can specify a directory for your functions and even set up routing rules. For example:
# netlify.toml
[build]
functions = "netlify/functions"
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
This configuration tells Netlify to look for functions in the netlify/functions directory and to route any requests starting with /api/ to your Netlify Functions. So, a request to /api/time would execute your time.js function.
One aspect that often trips people up is cold starts. Because Netlify Functions are serverless, the underlying execution environment might not be running when a request comes in. In such cases, AWS needs to provision a new instance, which adds latency to the first request after a period of inactivity. This "cold start" can be mitigated by keeping functions warm with periodic pings, but it’s an inherent characteristic of many serverless platforms.
The next logical step is to explore how to handle dynamic routing and pass parameters to your Netlify Functions.