Skip to content
For the complete Netlify documentation index, see llms.txt. Markdown versions of any documentation page are available by appending .md to its URL.

API reference

This page documents the entry file of a server, how to configure it, the request context, and the @netlify/server module. For how instances start and stop, refer to Lifecycle.

A server is defined in a single file at netlify/server/index.ts (or .mts, .js, .mjs). A project can have one server, so the build fails if this directory contains more than one of these files.

Netlify loads your entry file once per instance. When an instance starts, Netlify:

  1. Sets the PORT environment variable to a free port, unless you set it yourself.
  2. Imports the entry file, which runs its top-level code. If that code uses top-level await, Netlify waits for it.
  3. Runs your start hooks, if you registered any.
  4. Looks at the entry file's default export, and uses the first row of the following table that matches:
Default exportHow Netlify runs itWebSockets
A Node.js http.Server that doesn't call listen()Sends requests to that serverThrough the server's upgrade event, so libraries such as ws or Socket.IO work as usual
An object with a fetch method, as defined by the Fetchable specification, such as a Hono appCalls fetch(request, context) for each requestWith upgradeWebSocket
An Express or Connect app (a function with handle and use methods), or any function that takes three or more parameters, such as (req, res, next)Hosts it as a Node.js request listener in its own HTTP serverNot supported, answered with a 501
Any other functionCalls it as a handler, (request, context) => responseWith upgradeWebSocket
Anything else, or no default exportWaits for your code to start an HTTP server, then sends requests to itThrough the server's upgrade event

The instance receives no traffic until all these steps have finished. If any step throws an error or exception, the instance fails to start.

For examples, refer to Handler, Your app has a fetch method, Your app is a request listener, and Your framework starts its own server.

Whatever your entry file exports, your code can read the request's context with getContext(), and the entry file can export a config object.

A few things to know:

  • A function with two parameters, (req, res), looks the same as a handler, so it's called as one. To run it as a Node.js request listener, wrap it in a server, as described in Your app is a request listener.
  • A server your code starts itself must start listening within 10 seconds of your start hooks finishing. Listening on process.env.PORT is the most reliable option, but it can listen on any port that isn't reserved. If your code starts more than one server, Netlify uses the one listening on process.env.PORT, and the instance fails to start if none of them is.
  • Only Node.js HTTP servers are supported, such as ones created with node:http or by a framework. Other kinds of servers, such as a raw TCP server, aren't.

A handler is a function that takes a Request and a Context, and returns a Response or a promise that resolves to one. It's the same signature as Netlify Functions and Netlify Edge Functions.

netlify/server/index.ts
import type { Context } from "@netlify/server";
export default async (req: Request, context: Context) => {
return new Response(`Hello from ${context.geo.city ?? "somewhere"}!`);
};

The Config object configures how a server is deployed and which requests it receives. Export it from your entry file as a config constant, or place it inside the config property of a default export that's a Fetchable. You can also set the same properties in the [server] section of netlify.toml.

For descriptions of each setting with full examples, refer to Configuration.

netlify/server/index.ts
import type { Config } from "@netlify/server";
export default async () => new Response("Hello, world");
export const config: Config = {
region: "fra",
size: "medium",
};

The build fails if the configuration contains a property that isn't listed below, or a value that isn't valid.

One or more URL paths that the server doesn't handle. Refer to Excluded paths.

An array of glob patterns for additional files to include in the server's bundle. Refer to Included files.

One or more URL paths that the server handles. Defaults to /*. Refer to Paths.

The airport code of the region the server runs in, cmh or fra. Defaults to cmh. Refer to Region.

How many instances the server runs, as an object with min and max. min defaults to 0, and max to the highest your plan allows. Refer to Scale.

The size of the instances the server runs on, small, medium, or large. Defaults to small. Refer to Instance size.

The Context object holds information about the request being handled. A handler or a Fetchable receives it as its second argument; any other code can read it with getContext().

It has the following properties.

An object with the id of the team the site belongs to.

An interface for reading and setting cookies, which works whatever your entry file exports:

  • cookies.get(name) reads a cookie from the incoming request.
  • cookies.set(options) sets a cookie on the outgoing response, using the same options as CookieStore.set.
  • cookies.delete(name) or cookies.delete(options) tells the client to delete a cookie, following CookieStore.delete.

An object with information about the deploy the server belongs to:

  • branch: the Git branch the deploy was built from.
  • context: the deploy context, such as production or deploy-preview.
  • id: the deploy's unique ID.
  • published: whether the deploy is the site's published deploy.
  • skewProtectionToken: a token that identifies the deploy in HTTP requests when skew protection is enabled.

An object with the visitor's geolocation:

  • city: the name of the city.
  • country: an object with the country's ISO 3166 code and its name.
  • latitude and longitude: the coordinates of the location.
  • postalCode: the postal code, in the format of the visitor's region.
  • subdivision: an object with the ISO 3166 code of the country subdivision and its name.
  • timezone: the timezone of the location.

The visitor's IP address. In an app built with a framework or a Node.js server, req.socket.remoteAddress and the helpers built on it, such as Express's req.ip, report the same address.

The Netlify request ID, such as 01FDWR77JMF2DA1CHF5YA6H07C. Netlify adds it to anything your code logs with console while handling the request.

An object with the region the server runs in, as the airport code its configuration names, such as cmh.

An object with information about the site:

  • id: the site's unique ID.
  • name: the site's name, which is also its Netlify subdomain.
  • url: the site's main address, either its Netlify subdomain or its custom domain.

The URL of the request, as a URL object.

The Netlify user who logged in to view the site, when it's a private project or uses team login protection, or undefined otherwise. An object with the following properties:

  • id: the user's Netlify user ID.
  • email: the email address on the user's Netlify account, if available.
  • expiresAt: when the user's access to the site expires, as a Date.

A function that keeps the instance running until a promise settles, for work that should carry on after you respond. Refer to Background work.

@netlify/server provides the types and helpers for building a server. It's also what gives your code access to the lifecycle hooks, which let you run code when an instance starts (onStart) and when it shuts down (onShutdown), and report how busy it is (onHealthCheck).

Here's how to install it:

Terminal window
npm install @netlify/server

Returns the Context object of the request being handled. It works anywhere in the code that runs for a request, such as a framework's route handler or middleware, so it's the way to access the context in an app built with a framework.

import { getContext } from "@netlify/server";
app.get("/", (req, res) => {
const { geo } = getContext();
res.send(`Hello from ${geo.country?.name ?? "somewhere"}!`);
});

The Context object of the current request.

Throws when called outside a request, for example at the top level of the entry file or in a lifecycle hook.

Registers a hook that runs when an instance starts, after the entry file has loaded and before the instance receives any traffic. If the hook returns a promise, the instance waits for it, and fails to start if it rejects.

This is useful for work that should run once the whole app has been set up, such as connecting to a database, or for libraries that need to prepare something before the server takes traffic.

Start hooks can use Netlify Database, Netlify Blobs, the AI Gateway, and your environment variables, the same as a request can. Keep them short, since the instance receives no traffic until they finish, and an instance that takes too long to start fails to start.

netlify/server/index.mjs
import express from "express";
import { onStart } from "@netlify/server";
const app = express();
onStart(async () => {
await db.connect();
});
app.listen(process.env.PORT);

Netlify sends no traffic to your server until your start hooks have finished, even if it's already listening.

  • hook: a function that returns nothing or a promise. Hooks run one at a time, in the order you registered them.

Call onStart while the entry file loads. Calling it after the instance has started throws an error, since the hook would never run.

Registers a hook that runs as soon as an instance starts shutting down, when it stops receiving new traffic. Requests, WebSocket connections, and background work already in progress keep running, so use the hook to explicitly stop work that would otherwise keep running, such as a job queue or open WebSocket connections.

To wait for the work in progress before closing a resource it may still be using, such as a database pool, await drained:

import { onShutdown } from "@netlify/server";
onShutdown(async ({ drained }) => {
queue.stop();
await drained;
await pool.end();
});

Refer to Shutdown for how long each step can take.

  • hook: a function that receives an object with the following property, and returns nothing or a promise. Hooks run in parallel, so one that awaits drained doesn't hold back the others. A hook that throws is logged, and the others keep running.
    • drained: a promise that resolves once the requests, WebSocket connections, and background work in progress have all finished, or once Netlify stops waiting for them. It never rejects.

Registers a hook that reports how busy your server is, adding the resources only your code knows about, such as a database connection pool or a job queue, to the event loop and memory readings Netlify already takes. Netlify uses these reports to decide which instances receive new requests. Refer to Load-aware routing for how it uses them.

netlify/server/index.js
import { onHealthCheck } from "@netlify/server";
onHealthCheck(() => ({
utilization: {
db_pool: (pool.totalCount - pool.idleCount) / pool.options.max,
},
namedMetrics: {
db_pool_waiting: pool.waitingCount,
},
}));

Netlify calls your hook in the background, at most once per interval, and uses its latest result, so a hook can be synchronous or asynchronous. A hook that makes a network call can set a longer interval:

onHealthCheck(
async () => {
const { waiting } = await queue.getJobCounts("waiting");
return { utilization: { job_queue: Math.min(1, waiting / MAX_QUEUE) } };
},
{ interval: 10_000 },
);

You can register as many hooks as you like, including from libraries.

  • hook: a function that returns a health report, or nothing, directly or as a promise.
  • options (optional): an object with the following property:
    • interval: how often to call the hook, in milliseconds. Defaults to 1000, which is also the minimum.

Every property is optional:

  • utilization: an object with one entry per resource, each with how busy it is as a fraction of its capacity, between 0 and 1. The keys are names you choose for your app's own resources, as many as you need, such as db_pool or job_queue. Netlify looks at the highest entry across all your hooks and its own readings. The names event_loop and memory are reserved for Netlify's own readings.
  • applicationUtilization: a number between 0 and 1 that replaces the highest entry as the instance's overall figure, for apps whose busiest resource doesn't reflect how much more work they can take. If more than one hook sets it, the highest value is used.
  • namedMetrics: an object of values recorded for observability, such as a queue's length or a latency in milliseconds. Unlike utilization, they don't affect routing or scaling.

Utilization values outside 0 and 1 are clamped to that range, and entries that aren't numbers are ignored.

The health report is based on ORCA (Open Request Cost Aggregation), the open standard from the CNCF xDS project for backends to report how loaded they are, which gRPC and Envoy also use. utilization, applicationUtilization, and namedMetrics map to ORCA's utilization, application_utilization, and named_metrics fields.

If a hook throws, or returns nothing, it contributes nothing that time, and your instance is never stopped or replaced because of it. Netlify counts hooks that throw and logs their errors.

Keep hooks cheap, since they run for as long as the instance does. Prefer reading values your code already keeps in memory over querying another service each time.

Upgrades a request to a WebSocket connection. You get back a browser-style WebSocket to talk to the client, and a Response to return from your handler.

netlify/server/index.ts
import { upgradeWebSocket } from "@netlify/server";
export default async (req: Request) => {
if (req.headers.get("upgrade") === "websocket") {
const { socket, response } = upgradeWebSocket(req);
socket.onmessage = (event) => {
socket.send(`Echo: ${event.data}`);
};
return response;
}
// The same path can serve regular HTTP requests too.
return new Response("Connect with a WebSocket client");
};

upgradeWebSocket works when your entry file exports a handler or a Fetchable. When it exports a server or starts one, attach a WebSocket library such as ws or Socket.IO to that server instead, as described in Real-time.

  • request: the Request your server received. Throws if the request wasn't received through Netlify Server.
  • options (optional): an object with the following properties:
    • protocol: the subprotocol to accept, which becomes the client's WebSocket.protocol. Should match one of the protocols the client asked for.
    • idleTimeout: the number of seconds between pings to the client. If the client hasn't answered the previous ping when the next one is due, the connection is closed. Pings are disabled when this isn't set.

An object with the following properties:

  • socket: a WebSocket for the server side of the connection.
  • response: the Response that completes the upgrade. Return it from your handler as is.

Returns the WebSocket connections open on this instance, from every call to upgradeWebSocket. Connections to other instances aren't included, and neither are connections that a library such as ws accepts on your own server.

Use it to send a message to every client connected to the instance, or to close every connection when the instance shuts down. Closing with code 1012 tells clients that the server is restarting, so they can reconnect straight away and reach a new instance.

import { getWebSockets, onShutdown } from "@netlify/server";
onShutdown(() => {
for (const socket of getWebSockets()) {
socket.close(1012, "Server restarting");
}
});

An array with a WebSocket for each open connection.

The server side of a WebSocket connection, with the same interface as the browser's WebSocket. It has the send() and close() methods, the readyState, protocol, extensions, bufferedAmount, and binaryType properties, and the open, message, close, and error events, available both through addEventListener() and the on* properties.

You get one from upgradeWebSocket rather than by creating it yourself.

The event dispatched when a WebSocket connection closes, with the same code, reason, and wasClean properties as the browser's CloseEvent.

The following TypeScript types are available: