Maybe we do need a server
However you'll quickly hit a point where you need just a tiny taste of a server. This usually happens when you have an API secret that you don't want to leak—if all your code is client-side there's no way to hide anything.
Have we lost all the nice benefits of our static app? Do we have to maintain and deploy a separate backend repo (or try and deploy both from one repo)? It seems like overkill to have to build an entire Express app when all you want is a single endpoint that takes your request, attaches an API secret and forwards it on.
Enter Netlify Functions
As usual Netlify is out there making my job easier. Their "Functions" service allows you to write and deploy backend code from within the same repo as your frontend. These are technically "serverless functions" (or "lambdas" in Amazon-speak), but what that really means is a backend endpoint that runs a function whenever it gets a request. It's like on-demand Express routes.
Netlify Dev
Before we begin we should install the Netlify CLI. It allows you to run the entire Netlify production environment locally on your machine. This lets you test Functions, redirects and other config you may have set up.
You can npm install -g netlify-cli
to give you access to the netlify
command anywhere. You can now run netlify dev
inside a project and it should detect the project type and start up a dev server automatically.
Getting started with Functions
First we need to tell Netlify where our Functions will live. Create a netlify.toml
file at the root of your project. This allows us to configure all of Netlify's features within our repo.
[build]
functions = "functions"
Now Netlify knows where to put them we can use the CLI to create one for us. Run netlify functions:create
. It should ask you to choose a template—for now pick the basic hello world example.
You should see a new file at functions/hello/hello.js
that contains this code:
exports.handler = async (event, context) => {
try {
const subject = event.queryStringParameters.name || "World";
return {
statusCode: 200,
body: JSON.stringify({ message: `Hello ${subject}` }),
};
} catch (err) {
return { statusCode: 500, body: err.toString() };
}
};
Important: the export must be named "handler" for this to work.
Anatomy of a Function
Each Function is passed two arguments: the request object (commonly named event
) and the context
object (containing info about e.g. authentication).
event
has properties like the request querystring and body ready for you to use.
When you're ready to respond to the client you return an object with at least statusCode
and body
properties.
Async vs callback
The example above uses an async
JS function. This allows you to simply return an object for the response. Async functions always return promises, so Netlify will wait for the promise to resolve before sending the response.
If you don't like async
you can also use callbacks: the handler function receives a third callback argument that you can call with the response object:
exports.handler = (event, context, callback) => {
callback({
statusCode: 200,
body: "hello",
});
};
Sending requests
The endpoint is defined by the filesystem. Since our file is named hello.js
it'll be the /hello
route.
This means a request sent to /.netlify/functions/hello?message=everyone
would receive a response of { "message": "Hello everyone" }
.
Note the .netlify/
part—this namespaces Netlify stuff so it doesn't clash with routes your actual app might be using.
Using environment variables
If you're using netlify dev
to run your site locally it will automatically have access to environment variables you have set in the Netlify UI. So if you've created an environment variable in the UI named "API_SECRET" you should be able to access it in your Functions with process.env.API_SECRET
.
First you will have to authorize the Netlify CLI by running netlify login
.
If you have already deployed this repo to Netlify you can run netlify link
to link the Netlify CLI with the deployed site.
If you haven't deployed it yet you can run netlify init
to link and deploy right from the command line.
Once you've linked the site you should see a .netlify/state.json
file created, which contains the "siteID"
referring to your deployed site.
Multiple users
There is a downside here: you have to log in to the Netlify account that owns the site in order to link them and access the API secrets. The easiest way to work around this if you have multiple people working on the project is to create a shared account you can all log in to.
Alternatively you can pay for Netlify, which allows you to have multiple users in your team.
Using with Create React App
If you use netlify dev
to run your local environment you don't need any special config. Running that command will start both the CRA server (on port 3000
by default) and the Functions server (on a random port). It will also start your entire combined app on port 8888
. This is where the magic happens: your React code can now make requests to /.netlify/functions/hello
.
Important note: the React app on port 3000
won't work as it won't be able to reach the Functions. Make sure you're using the Netlify-Dev-served version on 8888
.
Without Netlify Dev
If you can't or don't want to use Netlify Dev it's a little more complicated. You'll need to run your Functions server manually (say on port 9000
), then set up a proxy in your React app so that requests get forwarded to that port. Netlify have a good example project with this set up (but Netlify Dev really is a lot easier).