Learning objectives
After reading this article you will be able to:
- Prepare a Node.js project to run outside your machine
- Write a server that reads its port from the environment and shuts down cleanly
- Verify the deploy and fix the most common errors
On this page
- What do you need before hosting?
- How do you set up the project?
- How do you write a production-ready server?
- How do you handle environment variables and secrets?
- How do you upload the code and start the process?
- How do you check that it worked?
- What are the most common errors?
- How to do this on Square Cloud
- FAQs
A Node.js app works on your machine because that machine has the right Node, the installed dependencies and the variables you configured. Hosting means reproducing that environment on a server that stays on all the time and that other people can reach.
The path is the same for an API, a site with a server or a bot: prepare the project, upload the code, install dependencies and start the process. This guide uses an Express API as the example, but the steps apply to any Node.js app. For the big picture, see what is deployment.
What do you need before hosting?#
- A defined Node version. Pick an LTS release and use the same one locally and in production.
- A complete
package.json, with dependencies and a start script. - A committed
package-lock.json, so installs are identical on any machine. - A place to run it: a VPS, a container or a hosting platform. The difference between them is covered in VPS vs cloud platform.
Which LTS? For a new project, use the current Active LTS of Node.js, as long as your dependencies support it, and declare it in package.json. The official release schedule shows which version is in that phase and how long each one gets fixes.
How do you set up the project?#
Create the minimal structure:
mkdir my-api && cd my-api
npm init -y
npm install expressEdit package.json for ES modules, with the Node version and scripts:
{
"name": "my-api",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"engines": { "node": ">=24" },
"scripts": {
"start": "node index.js",
"dev": "node --watch --env-file-if-exists=.env index.js"
},
"dependencies": {
"express": "^5.0.0"
}
}The engines field documents the expected version, and the start script is the command the server will run. dev uses two built-in Node flags: --watch restarts the process when a file changes, and --env-file-if-exists loads variables from a .env file (no error if it is missing), with no need for the dotenv package.
How do you write a production-ready server?#
A test server becomes a production server when it reads the port from the environment, keeps secrets out of the code and handles the shutdown signal.
// index.js
import express from "express";
const app = express();
const port = Number(process.env.PORT) || 3000;
app.get("/", (req, res) => {
res.send("Hello, world!");
});
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
const server = app.listen(port, "0.0.0.0", () => {
console.log(`Server listening on port ${port}`);
});
// On SIGTERM (restart or deploy), stop accepting new
// connections and finish the ones in progress.
process.on("SIGTERM", () => {
server.close(() => process.exit(0));
});Why each part matters:
process.env.PORT: the platform or server chooses the port and passes it in a variable. Hardcoding3000only works on your machine. The|| 3000is the development fallback./healthroute: gives you (and external monitors) a cheap address to check that the app responds.SIGTERM: the signal the system sends to ask a process to stop. Once you register a listener, Node no longer exits immediately (its default behavior), so you must end the process explicitly, as in the example.
Test locally with npm run dev and open http://localhost:3000/health.
How do you handle environment variables and secrets?#
Tokens, database passwords and API keys live in environment variables, never in the repository. Read them with process.env.NAME. Locally, use a .env file and add it to .gitignore; in production, set each variable in your provider's dashboard or in the server process. What are environment variables covers the pattern in detail.
Fail early when a required variable is missing. Instead of an obscure error later on, you get a clear message at startup:
if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL is not set");
}How do you upload the code and start the process?#
The general flow, on any host:
- Upload the code without
node_modulesand without.env(use a.gitignore). - On the server, install the exact lockfile dependencies:
npm ci --omit=dev. - Set the environment variables, including
NODE_ENV=production. - Start with
npm start(ornode index.js). - Keep the process alive: on a VPS, with a supervisor such as systemd or PM2; in containers and platforms, with their restart policy. See also how to package it with Docker.
If your app is an API, review the API production checklist before opening it to the public.
How do you check that it worked?#
After deploying, confirm in three steps:
- Logs: the "Server listening on port" line should appear with no errors after it.
- Request: call
/healthon the public address and expect{"status":"ok"}. - Restart: restart the app and confirm it comes back on its own.
What are the most common errors?#
Error: listen EADDRINUSE: address already in use: another process already holds the port. Stop the old instance or change the port.Cannot find module 'express': dependencies were not installed, or the package is indevDependenciesand you installed with--omit=dev. Move it todependencies.SyntaxError: Cannot use import statement outside a module:"type": "module"is missing frompackage.json, or you useimportin a file treated as CommonJS.- App starts but the site won't open: the server listens on a fixed port instead of
process.env.PORT, or only onlocalhost. In containers, listen on0.0.0.0. - Works locally, crashes in production: different Node version, missing environment variable or not enough memory. Compare what changed between environments.
How to do this on Square Cloud#
The flow in this guide barely changes on Square Cloud. You upload the project as a ZIP in the dashboard, with the CLI, through GitHub (a push triggers the deploy) or with the VS Code extension, and the deploy form detects index.js as the main file. The squarecloud.app file is optional: the dashboard generates it on deploy. If you prefer the package.json script, the start command field accepts any shell command, such as npm start or npm run migrate && npm start.
The install follows two rules that explain errors from the list above. The platform runs npm install only when node_modules is missing, so upload the project without that folder. It also installs with NODE_ENV=production: anything in devDependencies is left out, and the result is Cannot find module. There is no build step; to compile, use external CI such as GitHub Actions, or put the build in the start command. The TypeScript runtime runs your code without a separate compile step.
The port comes from the PORT variable, which the platform sets, and the server listens on 0.0.0.0, as in the example. Environment variables are set per application in the dashboard (which imports a .env), already exist during the install and apply on the next restart, with no rebuild. The container stays on, and automatic restart, on by default for dashboard deploys, brings back a process that exited with an error, so you can skip PM2. Running out of memory stops the app until you raise its RAM. The npm install output and the "Server listening on port" line show up in the real-time logs.
Each web application uses at least 512 MB of the plan's RAM, and Hobby starts at R$12.49 per month with 1 GB. Node.js is one of several runtimes, alongside Python, Go and Java: see the runtimes available today on Runtimes and the plans on API hosting.
API hosting
APIs in always-on containers, with no cold starts
FAQs
Which Node.js version should I use in production?
Use the current Active LTS of Node.js, which gets security fixes for longer. Check the official Node.js release schedule to see which version that is before you pin it in your project.
Do I need PM2 to keep my app running?
It depends on where you run it. On a server you administer, a process supervisor restarts the app if it crashes. On platforms that already restart the process for you, PM2 is usually unnecessary.
Should I upload the node_modules folder?
Usually not. Upload package.json and package-lock.json and let the environment install dependencies with npm ci. node_modules can be huge and depends on the operating system.
Can I host a Node.js app written in TypeScript?
Yes. You can compile to JavaScript before deploying, or use a platform that runs TypeScript directly. Running the compiled code is usually the most predictable option.
More in APIs
See all 15 articles- How to host an APIHosting an API means running its HTTP server around the clock at a public address with HTTPS. Step-by-step with Node.js and Python, testing and common errors.
- How to host a Python app or scriptTo host a Python app or script, pin the version, isolate dependencies in a venv, read secrets from the environment and handle SIGTERM. Step-by-step guide.
- What is a cron job? Syntax and examplesA cron job is a task that runs on its own at times set by a five-field expression. Learn the syntax, examples, common mistakes and how to schedule jobs in Node.js.
- Production checklist for your APIA production checklist for your API: environment variables, health check, graceful shutdown, standard errors, authentication, rate limiting, CORS and logs.