Netlify Lambda Functions Tutorial
By Flavio Copes
Learn how to create a modern Netlify Function with TypeScript, Web Request and Response APIs, custom routing, local testing, and environment variables.
Netlify Functions add server-side endpoints to a site without you managing a server.
A modern function receives a standard Web API Request, gets Netlify-specific information from a Context, and returns a Response.
The old callback-based exports.handler syntax still appears in many tutorials, but new functions should use a default export.
Create the project
Start in an existing site or create a small project:
npm init -y
npm install @netlify/functions
npm install --save-dev netlify-cli
Netlify’s default functions directory is netlify/functions/.
Create netlify/functions/hello.mts:
import type { Config, Context } from '@netlify/functions'
export default async (request: Request, context: Context) => {
const name = new URL(request.url).searchParams.get('name') ?? 'friend'
return Response.json({
message: `Hello, ${name}!`,
requestId: context.requestId
})
}
export const config: Config = {
path: '/api/hello',
method: 'GET'
}
The config export gives the function a clean route. Without a custom path, a function named hello is available at /.netlify/functions/hello.
The functions overview documents the current handler and routing APIs.
Run the function locally
Start Netlify’s local development environment:
npx netlify dev
Call the function from a browser or another terminal:
curl "http://localhost:8888/api/hello?name=Flavio"
The port can differ if it is already in use. Use the URL printed by Netlify CLI.
Read request data
The handler receives a standard Request, so you use familiar Web APIs.
Read the HTTP method:
request.method
Read a header:
request.headers.get('authorization')
Parse JSON from a request body:
const data = await request.json()
Read query parameters:
const url = new URL(request.url)
const page = url.searchParams.get('page')
Validate untrusted input before using it. Return a clear 400 response when the request is invalid.
Return responses
Return plain text:
return new Response('Created', {
status: 201
})
Return JSON:
return Response.json(
{ error: 'Not found' },
{ status: 404 }
)
Return HTML by setting the content type explicitly:
const html = '<h1>Hello from a Netlify Function</h1>'
return new Response(html, {
headers: {
'content-type': 'text/html; charset=utf-8'
}
})
Without that header, the browser can treat the response as plain text instead of rendering the markup.
Return a method error and advertise the supported method:
return new Response('Method not allowed', {
status: 405,
headers: {
Allow: 'POST'
}
})
The method field in the function config can restrict which methods route to the handler, but your application should still return intentional responses for the methods it supports.
Use path parameters
Configure a dynamic path:
import type { Config, Context } from '@netlify/functions'
export default async (request: Request, context: Context) => {
return Response.json({
id: context.params.id
})
}
export const config: Config = {
path: '/api/items/:id',
method: 'GET'
}
A request to /api/items/42 sets context.params.id to 42.
Store secrets as environment variables
Set a secret through the Netlify UI or CLI:
npx netlify env:set API_KEY "your-secret-value" \
--secret \
--scope functions
Read it only in server-side code:
const apiKey = process.env.API_KEY
The value passed on the command line may remain in shell history. For sensitive production values, prefer the Netlify UI or another approved secret-management workflow.
Do not commit .env files containing secrets. Do not return secrets to the browser or expose them through environment-variable prefixes intended for client-side code.
Netlify documents function scopes and configuration in its environment variable guide.
Install npm packages
Install a function dependency in the project root:
npm install axios
Commit package.json and the lockfile. Do not commit node_modules. Netlify installs dependencies during the build and bundles the packages imported by each function.
Use a regular ESM import in a modern function:
import axios from 'axios'
The .mts extension in the first example makes the module format explicit for TypeScript. For JavaScript, use .mjs, or configure ESM for .js files with "type": "module" in package.json. Modern Netlify Functions use a default export; the older named handler export and callback response format belong to the legacy API.
Deploy the function
Commit the function with the rest of the site. A Git-connected Netlify project bundles and deploys it during the normal deploy.
For a manual draft deploy:
npx netlify deploy
Publish only after testing the draft:
npx netlify deploy --prod
Open the deploy summary to verify that Netlify detected the function.
When to write a raw Netlify Function
Write a function directly when you need a standalone API endpoint, webhook, scheduled task, or background task.
Frameworks such as Next.js, Astro, Nuxt, and SvelteKit often generate functions through their Netlify adapters. In that case, use the framework’s route APIs for normal application pages and endpoints instead of duplicating the routing layer.
Start with Netlify’s function setup guide and check the current runtime limits before designing long-running or large-payload work.
Related posts about services: