Skip to content

DocsDeploy guides

Express

What an Express app needs to run on the platform: the port and address, the start command, TypeScript, data and files, with a small example.

Your app needs How
A package.json At the top of the repository, or in one folder you set as the root directory
A start script "start": "node server.js" in package.json
The right port Listen on process.env.PORT
The right address Listen on all interfaces: no host, or 0.0.0.0
A quick start Be listening within 2 minutes
No files of its own Keep data in a database, not on the disk of the app

Check your app checks all of these for you.

server.js
const express = require('express');
const app = express();
app.get('/', (request, response) => {
response.send('Hello');
});
// The platform sets PORT. 3000 is for your own machine.
const port = process.env.PORT || 3000;
const server = app.listen(port, '0.0.0.0', () => {
console.log(`Listening on ${port}`);
});
// Finish open requests when the platform stops the app.
process.on('SIGTERM', () => {
server.close(() => process.exit(0));
});
package.json
{
"name": "my-app",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^5.1.0"
},
"engines": {
"node": "22"
}
}

Push these two files and the lockfile (package-lock.json), create an app from the repository, and it runs. Nothing else to set.

The platform sets the PORT variable and sends every request to that port. It is 3000 unless you change it in the settings.

  • Read the port from PORT. A port written into the code only works by accident.
  • Listen on all interfaces. Give no host, or 0.0.0.0. A server on localhost or 127.0.0.1 cannot be reached, and the deployment fails.
  • One port. Only this port gets traffic. A second server on another port is not reachable.
  • Plain HTTP. HTTPS is handled before a request reaches your app. Do not load certificates in the app.

The start command is npm run start, so your package.json needs a start script. Without one, the main file is started with node.

Start the server with node. Tools that watch files and restart (nodemon, for example) are for your own machine, and they are usually in devDependencies, which the running app does not have.

The new version has 2 minutes to start listening. If it takes longer, the deployment fails and the old version stays.

Do slow one-off work before the app starts, not inside it. Database migrations belong in deploy commands: they run once for every deployment, before the new version takes traffic.

Build to JavaScript, then start the built file:

{
"scripts": {
"build": "tsc",
"start": "node dist/server.js"
}
}

The build command npm run build is filled in when your package.json has a build script.

To stop a replica, the platform sends SIGTERM and waits 30 seconds. Close the server on that signal, as in the example, so open requests can finish.

The disk of the app is temporary. A file written while the app runs is gone after the next deployment or restart, and it can be gone after the app has slept.

  • Database. Use a hosted database and put its address in a variable, DATABASE_URL for example. SQLite in a file does not survive.
  • Uploads. Send them to a file store outside the app, not to a folder.
  • Sessions. express-session keeps sessions in memory unless you give it a store. In memory they are lost on every restart and are not shared between replicas. Use a store backed by your database.

An idle app sleeps and wakes only for a request. A timer, a cron job or a queue worker inside the app does not run while it sleeps.

If your app does work without a visitor, keep Keep one replica always on switched on under Scaling. With more than one replica, every replica runs the job. See Sleep and wake.

Settings and secrets come from environment variables you add in the dashboard. A .env file that is not committed does not exist on the platform.

Three variables are set for you: PORT, NODE_ENV (always production) and APP_URL (the address of your app).