Choosing a Database for a Discord Bot: SQLite, PostgreSQL, or Redis
Select storage based on data durability, concurrent writes, and operational limits.
Read article ↗Build and deploy a working Node.js HTTP API with port configuration, health checks, tests, graceful shutdown, and practical troubleshooting.
Your first Node.js deployment should be boring in the best possible way: a small application, a clear startup command, predictable logs, and a repeatable way to test whether it works. Many beginners start by uploading a large project, then cannot tell whether a failure comes from their code, the runtime, a missing dependency, or a networking rule. This guide avoids that problem. We will create a working HTTP service using only Node.js built-in modules, test it with real requests, and prepare it for a compatible hosting environment.
The example is deliberately independent of a particular hosting provider. You can run it on your computer first and then use the same project with a provider that permits a Node.js HTTP process. You must still follow the provider’s rules for assigned ports, resource limits, and public routing. An application that starts successfully is not necessarily reachable from the public internet.
The application will respond to three useful URLs. The root route returns a welcome message; /health reports basic process liveness; and /api/status returns JSON containing its uptime. Unknown URLs receive a real 404 status, and unsupported methods receive 405. These small details make the service much easier to test and debug than an example that always returns success.
We will also configure the startup command in package.json, validate the listening port, keep unnecessary files out of version control, and handle a shutdown signal. None of these ideas requires a large framework, and all remain valuable when you later move to Express, Fastify, or a full-stack application.
Install a maintained Node.js release appropriate for your hosting environment, along with npm. Open a terminal and confirm both executables are available:
node --version
npm --version
If your hosting service only provides an older Node.js release, check compatibility before building your project around new language features. Do not assume the version on your laptop matches the one on the server. You will also need a text editor and a second terminal for making HTTP requests. The examples below use curl for convenience, but a browser can test the homepage and health route.
Create a new directory and initialize a package manifest. Run these commands on your development machine, not in an unrelated application’s folder:
mkdir first-node-api
cd first-node-api
npm init -y
Replace the generated package.json with this minimal manifest:
{
"name": "first-node-api",
"version": "1.0.0",
"private": true,
"description": "A minimal deployable Node.js HTTP service",
"scripts": {
"start": "node server.js",
"check": "node --check server.js"
}
}
The start script is the instruction the host needs to launch the process. The check command catches JavaScript syntax errors without opening a port. Because this example uses only built-in modules, no external packages need to be installed. A real project with external dependencies should normally commit its lockfile so deployment installs can be reproduced.
Create server.js alongside package.json. Keep the entry point at the top level for now. A surprising number of startup failures are simply caused by a command referencing index.js when the actual file is named src/server.js, or by launching the correct command from the wrong working directory.
Copy the complete example below into server.js. It checks configuration before listening, implements the three routes, and logs a useful startup message without exposing environment variables.
const http = require("node:http");
const rawPort = process.env.PORT ?? "3000";
const port = Number(rawPort);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
console.error("PORT must be a valid TCP port from 1 to 65535");
process.exit(1);
}
const server = http.createServer((request, response) => {
const path = new URL(request.url, "http://localhost").pathname;
if (request.method !== "GET") {
response.writeHead(405, {
"Content-Type": "application/json; charset=utf-8",
"Allow": "GET"
});
response.end(JSON.stringify({ error: "Method not allowed" }));
return;
}
if (path === "/health") {
response.writeHead(200, {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "no-store"
});
response.end(JSON.stringify({ status: "ok" }));
return;
}
if (path === "/api/status") {
response.writeHead(200, {
"Content-Type": "application/json; charset=utf-8",
"Cache-Control": "no-store"
});
response.end(JSON.stringify({
service: "first-node-api",
uptimeSeconds: Math.floor(process.uptime())
}));
return;
}
if (path === "/") {
response.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
response.end("Hello from your Node.js application!");
return;
}
response.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
response.end(JSON.stringify({ error: "Not found" }));
});
server.on("error", (error) => {
console.error("HTTP server error:", error.message);
process.exitCode = 1;
});
server.listen(port, "0.0.0.0", () => {
console.log(`Server listening on port ${port}`);
});
function shutdown(signal) {
console.log(`Received ${signal}; stopping HTTP server`);
server.close((error) => {
if (error) {
console.error("Shutdown failed:", error.message);
process.exitCode = 1;
}
});
}
process.once("SIGTERM", () => shutdown("SIGTERM"));
process.once("SIGINT", () => shutdown("SIGINT"));
The explicit routes are easier to reason about than one broad catch-all handler. In a larger API, you would typically use a router or framework, but the underlying requirement remains the same: a request should receive the appropriate status, content type, and response body.
The application listens on 0.0.0.0, which means it accepts connections through available IPv4 interfaces inside its runtime environment. That does not expose arbitrary ports to the internet. A container may have its own network namespace, a hosting platform may assign a particular port, and a reverse proxy may be responsible for HTTPS and domains. Only use the allocation your provider documents.
Start by validating syntax:
npm run check
Then launch the service:
npm start
The console should display a message similar to Server listening on port 3000. Leave the process running, open another terminal, and send a request to the homepage:
curl -i http://127.0.0.1:3000/
You should see HTTP status 200 and the welcome text. If curl cannot connect, confirm the process is still running, check for an exception in the first terminal, and verify that nothing else occupies the port.
A startup message is not a substitute for testing behavior. Try these requests one at a time:
curl -i http://127.0.0.1:3000/health
curl -i http://127.0.0.1:3000/api/status
curl -i http://127.0.0.1:3000/missing
curl -i -X POST http://127.0.0.1:3000/
Expected status codes are 200, 200, 404, and 405 respectively. The health response should include {"status":"ok"}. The status route should report a service name and an uptime value. These checks confirm that route selection, HTTP methods, and error behavior work as intended.
PORT is one example of an environment variable: a configuration value provided to the process without editing application source. On Linux or macOS, you can temporarily override the default by running:
PORT=3100 npm start
Use port 3100 only if it is free on your local machine. On a hosting service, use the port the platform assigned instead. Avoid storing passwords or API tokens in source code. Sensitive configuration should be supplied through the hosting provider’s environment or secrets interface, with access limited to authorized collaborators.
Add a basic .gitignore file to keep local files out of new commits:
node_modules/
.env
*.log
An ignore rule does not erase credentials already committed in Git history. Rotate any leaked credentials and review where copies may remain.
Your minimum upload contains server.js and package.json. If you later add external packages, include the lockfile and install dependencies using the appropriate package-manager workflow. Avoid copying an arbitrary node_modules directory from a different operating system; native dependencies may not work across environments.
Create a compatible Node.js application or server in the host’s dashboard. Select an appropriate runtime, upload or deploy your project files, and configure the startup command as npm start or node server.js according to the platform’s form. Set the expected port allocation using the provider’s instructions. Then start the application and read its logs.
First confirm that the process launches without errors. Next test the public route or reverse-proxy address provided by the host, rather than assuming success from a green process indicator. Test the homepage and /health through HTTPS if the host publishes an HTTPS URL. If the internal route works but the public route fails, the issue may be DNS, TLS, proxy configuration, or network allocation rather than JavaScript.
On Volyx, check the current dashboard and documentation for the precise startup and network options available to your server; those options may differ by environment. This guide intentionally does not claim every server exposes the same ports or runtime versions.
Hosting platforms commonly send SIGTERM when stopping or restarting an application. Our example uses server.close() to stop accepting new connections and let normal shutdown proceed. A larger project may also need to close database connections, stop background workers, and apply a bounded shutdown timeout.
Before changing a working deployment, preserve the last successful version. Roll out one small update, confirm /health still works, and test at least one meaningful application action. If you change the listening port, dependency versions, or environment variables simultaneously, diagnosis becomes harder when something fails.
Check the project’s files and current directory. If the host starts index.js but your file is server.js, update the startup command. For external dependencies, confirm the package is declared and installed, and avoid mixing npm with another lockfile unexpectedly.
pwd
ls -la
node --version
Another process may already be listening on the selected port. Stop the duplicate process or choose an allocated unused port. Do not bind to random public ports or disable firewall rules just to work around an application configuration problem.
Inspect the route separately from the process. Test internal HTTP access, the proxy, and public DNS independently. A local success combined with a public failure often points to networking or TLS setup. Follow your provider’s supported approach instead of changing production proxy settings without a backup.
Our health route only confirms the HTTP handler responds. It does not prove that a future database, Discord connection, or external API is working. More advanced applications can expose a separate readiness check that verifies critical dependencies without leaking private diagnostic details.
Once you can deploy and verify this small service, add one feature at a time: structured request logging, automated tests, a database, or a framework. Keep a clean startup script and known working build. The best deployment workflow is one you can repeat and explain, not one that worked once by accident.
For authoritative references, consult the Node.js HTTP documentation, Node.js process documentation, and npm scripts documentation. For host-specific operations, refer to Volyx Documentation.
Explore more in-depth articles with code, visuals, and practical walkthroughs.
Browse all articles ↗